diff --git a/.gitignore b/.gitignore index 6ceac139f..a5f149548 100755 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ *.rej # OSX .DS_Store files .DS_Store +# version scripts (repo master only) +.version* Thumbs.db diff --git a/.openshift/action_hooks/deploy b/.openshift/action_hooks/deploy index c3bdf575a..bc3050339 100755 --- a/.openshift/action_hooks/deploy +++ b/.openshift/action_hooks/deploy @@ -197,22 +197,22 @@ echo "Try to add or update Hubzilla addons" cd ${OPENSHIFT_REPO_DIR} util/add_addon_repo https://github.com/redmatrix/hubzilla-addons.git HubzillaAddons -# Hubzilla themes -echo "Try to add or update Hubzilla themes" +# Hubzilla themes - unofficial repo +echo "Try to add or update Hubzilla themes - unofficial repo" cd ${OPENSHIFT_REPO_DIR} -util/add_theme_repo https://github.com/DeadSuperHero/hubzilla-themes.git DeadSuperHeroThemes +util/add_theme_repo https://github.com/DeadSuperHero/hubzilla-themes.git DeadSuperHeroThemes insecure -# Hubzilla ownMapp -echo "Try to add or update Hubzilla ownMapp" +# Hubzilla ownMapp - unofficial repo +echo "Try to add or update Hubzilla ownMapp - unofficial repo" cd ${OPENSHIFT_REPO_DIR} -util/add_addon_repo https://gitlab.com/zot/ownmapp.git ownMapp +util/add_addon_repo https://gitlab.com/zot/ownmapp.git ownMapp insecure -# Hubzilla Chess -echo "Try to add or update Hubzilla chess " +# Hubzilla Chess - unofficial repo +echo "Try to add or update Hubzilla chess - unofficial repo" cd ${OPENSHIFT_REPO_DIR} -util/add_addon_repo https://gitlab.com/zot/hubzilla-chess.git Chess +util/add_addon_repo https://gitlab.com/zot/hubzilla-chess.git Chess insecure -# Hubzilla Hubsites -echo "Try to add or update Hubzilla Hubsites" +# Hubzilla Hubsites - unofficial repo +echo "Try to add or update Hubzilla Hubsites - unofficial repo" cd ${OPENSHIFT_REPO_DIR} -util/add_addon_repo https://gitlab.com/zot/hubsites.git Hubsites +util/add_addon_repo https://gitlab.com/zot/hubsites.git Hubsites insecure diff --git a/CHANGELOG b/CHANGELOG index d708278b6..bfb5ad2b2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,169 @@ +Hubzilla 1.10 + Wiki: + Lots of enhanced functionality, usability improvements, and bugfixes from v1.8 + Turned into an optional feature (default on) but disabled in UNO + Sync: + Items are now relocated (links patched) when syncing to clones + Access Tokens: + New feature - allows members to create access controlled guest logins and create/share 'dropbox' style links to protected resources. + UI: + Use icons instead of iconic text constructs + Only request geolocation permission when creating a post, not on page load + provide 'redeliver' option on Delivery Report page for when things really stuff up + CalDAV/CardDAV management pages with heaps of functionality + Lib: + z_fetch_url() updated to accept different request methods and request bodies + item_store(), item_store_update() now return the stored items + vcard microformat changes to remain spec compliant + microformat meta tags added to post/comments + AbConfig API changed to use channel_id rather than channel_hash, which was overly complicated to use + SuperCurl class added to provide a framework for re-use of obscure CURL options + Allow absolute links to CSS/JS files on CDN + Add Let'sEncrypt intermediate cert to lib in case you forget to install it on the server + Update fullcalendar and jquery (3.1) libs + Update sabre/dav to 3.2.0 + Change content export from a month/year system to begin/end + Use streaming I/O for delivering large photos + Allow multiple App description files in a single plugin directory + optimise a couple of troublesome/inefficient SQL queries + avoid sending clone sync packets to dead sites + Resolved Issues: + channel home page not providing content to clients with javascript disabled + Replace '@' obfuscation with html entity rather than the unicode look-alike + xchan_query() failing to detect duplicates, resulting in inefficient queries + issues with 'use existing photo' for profile photo + layout editor "list all layouts" returned empty + oembed - better detect video file URLs so they aren't loaded into memory. + handcrafted bbcode tables could end up with way too much whitespace due to CRLF translation + refresh permissions whitescreen in 1.8 + force immediate profile photo update on local site + regression: 'save bookmarks' post action missing + +Hubzilla 1.8 + Administration: + Cleanup and resolve some edge cases with addon repository manager + Provide sort field and direction on all fields of account and channel administration tables + Rename 'user' administration to account administration to reflect its true purpose + 'safemode' tool to quickly disable and re-enable addons during a hypothetical upgrade crisis + Security: + Edited comments to private posts could lose their privacy settings under some circumstances + Provide zot-finger signatures to prevent a possible but rare exploit involving DNS spoofing and phishing + ACL selections: + Various improvements to the ACL editor to further simplify the concepts and make it more intuitive + Chat: + Notifications of chatroom activity using standard browser notification interfaces. + Themes: + Allow a theme:schema string to represent a valid theme name. This fixes issues with setting schemas on site themes. + Pubsites: + Show server role (identify UNO or basic sites as opposed to hubzilla pro) and link to statistics + Documentation: + Clarify privacy rights of commenters w/r/t conversation owners, as this policy is network dependent. + Wiki (Git backed): + Brand new feature. We'll call it experimental until it has undergone a bit more testing. + Account Cloning: + Regression on clone channel creation created a new channel name each time. + New issue (fixed) with directory creation on cloned file content + Content Rendering: + Add inline code (in addition to the existing code blocks) to BBcode + Add emoji reactions + Add emojis as extended smilies with auto-complete support + Emoji added as feature so it can be enabled/disabled and locked + Ability to configure the standard reactions available on a site basis + Disable 'convenience' ajax autoload on pgdn key, as it could lead to premature memory exhaustion + Photos: + Change album sort ordering, allow widgets and plugins to define other orderings + Apps: + Synchronise app list with changes to system apps + Preserve existing app categories on app updates/edits + Regression: fixed translated system app names + Architecture: + Provide autoloaded class files and libraries for plugins. + Further refactoring of session driver to sort out some cookie anomolies + Experimental PDO database driver + Creation of Daemon Master class and port all daemon (background task) interfaces to use it + Create separate class for each of 'Cron', 'Cron daily', and 'Cron weekly'. + Always run a Cron maintenance task if not run in the last four hours + Refactor the template classes + Refactor the ConversationItem mess into ThreadItem and ThreadStream + Refactor Apps, Enotify, and Chat library code + Refactor the various Config libraries (Config, PConfig, XConfig, AConfig, AbConfig, and IConfig) + Created WebServer class for top level + Remove mcrypt dependencies (deprecated in PHP 7.1) + Remove all reserved (including merely 'not recommended') words as DB table column names + Provide mutex lock on DB logging to prevent recursion under rare failure modes. + Bugfixes: + Remove db_close function on page end - not needed and will not work with persistent DB connections. + Undefined ref_session_write + Some session functions needed to be static to work with CalDAV/CardDAV + CLI interface: argc and argv were reversed + HTML entities double encoded in edited titles + Prevent delivering to empty recipients + Sabre library setting some security headers for SAML after we've emitted HTML content + Always initialise miniApp (caused obscure warning message if not set) + Block 'sys' channels from being 'random profile' candidates + DB update failed email could be sent in the wrong language under rare circumstances + Openid remote authentication used incorrect namespace + URL attached to profile "things" was not linked, always showing the "thing" manage page + New connection wasn't added to default privacy group when "auto-accept" was enabled + Regression: iconfig sharing wasn't working properly + Plugins: + CalDAV/CardDAV plugin provided + Issue sending Diaspora 'like' activities from sources that did not propagate the DCV + Allow 'superblock' to work across API calls from third party clients + statistics.json: use 'zot' as protocol + Issues fixed during testing of ability to follow Diaspora tags + Parse issue with Diaspora reshare content + Chess: moved to main repo, ported to 1.8 + +Hubzilla 1.6 + Cleanup and standardise the interfaces to the "jot" editor + Router re-written to support calling class object methods as controllers + All existing modules (160+) re-written as object classes + Plugin hook interface adapted to call static class methods + Context help improved dramatically with content for the most accessed pages. + Reverted a compatibility change to support GNU-social events. We copied their feed format and their feed format is wrong (XML namespace collisions). + Provide a querystring attribute to CSS/JS resources to avoid caching issues when our code changes (which is often). + Fix javascript detection and allow either positive or negative detection. + Refactor the plugin hook registration procedure, provide 'unregister all' ability. + Fix RSD (Real Simple Discovery) which has been broken for some time. + Update smarty library to 3.1.29 + Update jquery.textcomplete to 1.3.4 + Update font-awesome to 4.6.1 + Update SabreDAV to 3.0 (PHP version requirements prevent us from pushing it further at this time) + Help text added to cmdline utilities config and pconfig + Reworking of the database logging facility to avoid the rare but troublesome recursion when the log facility needed to query the DB internally to obtain config parameters. + Implement singleton delivery (emulate nomadic identity to singleton networks and services) + Fix empty album name in photo activities when photo is stored in top level folder. + Allow engineering units to be used in service class data size restrictions (400M, 1G, etc.) + Lots of work on bbcode auto-completion + Admin interface provided to manage external resource repositories + Oembed security reworked. Now all sources are filtered by default unless blocked. + Remove the date-string version and use only STD_VERSION + Add categories and categorisation filtering and the ability to edit all apps (including system apps) for a given channel + Ensure the ability to translate names of all system apps (except those provided in addons) + Provide ability to add categories to content from channel sources + Lots of work on the presentation of the ACL widget to enhance usability and intuitiveness + Allow somebody to follow a channel from a pasted redress containing a Unicode lookalike of the @ sign. + Add conditional syntax to Comanche (if/then/else) + Convert Comanche to an object class + Removed IE6 compatibility code + Explicitly close DB on shutdown/exit instead of allowing it to close naturally + Allowed delayed publish of webpages + Show current repository versions of master and dev on admin page and warn if your installation has fallen behind master + Provide some extra security checks to import data and files to prevent mischief + Block CalDAV/CardDAV namespace reserved words from being used as a channel nickname/redress since Sabre is somewhat inflexible in this regard + Plugins: + Diaspora + markdown translator work needed to eradicate the Diaspora Comment Virus. + upgrade all inbound paths with the most recent protocol changes (several of these) + convert 'diaspora_meta' (Diaspora Comment Virus) to iconfig and eradicate from sites with Diaspora disabled + implement social relay and allow following tags + upgrade statistics.json to NodeInfo. Currently hubzilla sites are tagged as 'redmatrix' because the NodeInfo schema lacks extensibility and project names are used to designate protocol compatibility rather than protocol names. + Std-embeds + New addon to allow a handful of corporate providers to run unfiltered embed code (youtube, vimeo, soundcloud) + Various: + upgrade font-awesome icons and adapt a few addons to Objects and the new hook interface and new controller interface + Hubzilla 1.4 [This list may appear brief, but encompasses a huge amount of re-writing and re-factoring of the internal code structure to gain long-term performance and stability and provide a standard diff --git a/LICENSE b/LICENSE index a2c2d1599..ab37f5ba7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2016 Hubzilla +Copyright (c) 2010-2016 the Hubzilla Community All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index ad7a4a9ca..9929f6a16 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,11 @@ Hubzilla - Community Server =========================== -Connected and linked web communities. -------------------------------------- +Groupware re-imagined and re-invented. +-------------------------------------- + +Connect and link decentralised web communities. +-----------------------------------------------

Installing Hubzilla @@ -44,4 +47,16 @@ Possible website applications include Installing Hubzilla

+**Who Are We and What Are Our Principles?** + +The Hubzilla community is powered by passionate volunteers creating an open source **commons** of decentralised services which are highly integrated and can rival the feature set of centralised providers. We are open to sponsorship and donations to cover expenses and compensate for our time and energy, however the project core is basically non-profit and is not designed for the purpose of commercial gain or exploitation. + +Some sites may include monetisation strategies such as subscriptions and *freemium* models where members pay for resources they consume beyond a basic level. The project community supports such monetisation initiatives (nobody should be forced to pay "out of pocket" to provide a service to others), but we maintain the **commons** to provide open and free access of the software to all. + +The software is not designed for data collection of its members or providing advertising. We don't have a need or desire for these things and feel that software built around these goals is poorly designed and represents compromised principles and ethics. + +As a project, we are inclusive of all beliefs and cultures and do what we are able to provide an environment that is free from hostility and harrassment. Whether or not we succeed in this endaevour requires constant vigilance and help from all members of the community, working together to build an inter-networking tool with amazing potential. + + + [![Build Status](https://travis-ci.org/redmatrix/hubzilla.svg)](https://travis-ci.org/redmatrix/hubzilla) diff --git a/Zotlabs/Access/PermissionLimits.php b/Zotlabs/Access/PermissionLimits.php new file mode 100644 index 000000000..909b654d5 --- /dev/null +++ b/Zotlabs/Access/PermissionLimits.php @@ -0,0 +1,36 @@ + $v) { + if(strstr($k,'view')) + $limits[$k] = PERMS_PUBLIC; + else + $limits[$k] = PERMS_SPECIFIC; + } + return $limits; + } + + static public function Set($channel_id,$perm,$perm_limit) { + ZLib\PConfig::Set($channel_id,'perm_limits',$perm,$perm_limit); + } + + static public function Get($channel_id,$perm = '') { + if($perm) { + return Zlib\PConfig::Get($channel_id,'perm_limits',$perm); + } + else { + Zlib\PConfig::Load($channel_id); + if(array_key_exists($channel_id,\App::$config) && array_key_exists('perm_limits',\App::$config[$channel_id])) + return \App::$config[$channel_id]['perm_limits']; + return false; + } + } +} \ No newline at end of file diff --git a/Zotlabs/Access/PermissionRoles.php b/Zotlabs/Access/PermissionRoles.php new file mode 100644 index 000000000..8b116adc5 --- /dev/null +++ b/Zotlabs/Access/PermissionRoles.php @@ -0,0 +1,215 @@ + [ + 'social' => t('Social - Mostly Public'), + 'social_restricted' => t('Social - Restricted'), + 'social_private' => t('Social - Private') + ], + + t('Community Forum') => [ + 'forum' => t('Forum - Mostly Public'), + 'forum_restricted' => t('Forum - Restricted'), + 'forum_private' => t('Forum - Private') + ], + + t('Feed Republish') => [ + 'feed' => t('Feed - Mostly Public'), + 'feed_restricted' => t('Feed - Restricted') + ], + + t('Special Purpose') => [ + 'soapbox' => t('Special - Celebrity/Soapbox'), + 'repository' => t('Special - Group Repository') + ], + + t('Other') => [ + 'custom' => t('Custom/Expert Mode') + ] + + ]; + + return $roles; + } + + + +} \ No newline at end of file diff --git a/Zotlabs/Access/Permissions.php b/Zotlabs/Access/Permissions.php new file mode 100644 index 000000000..61ea51a48 --- /dev/null +++ b/Zotlabs/Access/Permissions.php @@ -0,0 +1,116 @@ + t('Can view my channel stream and posts'), + 'send_stream' => t('Can send me their channel stream and posts'), + 'view_profile' => t('Can view my default channel profile'), + 'view_contacts' => t('Can view my connections'), + 'view_storage' => t('Can view my file storage and photos'), + 'write_storage' => t('Can upload/modify my file storage and photos'), + 'view_pages' => t('Can view my channel webpages'), + 'write_pages' => t('Can create/edit my channel webpages'), + 'post_wall' => t('Can post on my channel (wall) page'), + 'post_comments' => t('Can comment on or like my posts'), + 'post_mail' => t('Can send me private mail messages'), + 'post_like' => t('Can like/dislike profiles and profile things'), + 'tag_deliver' => t('Can forward to all my channel connections via @+ mentions in posts'), + 'chat' => t('Can chat with me'), + 'republish' => t('Can source my public posts in derived channels'), + 'delegate' => t('Can administer my channel') + ]; + + $x = array('permissions' => $perms, 'filter' => $filter); + call_hooks('permissions_list',$x); + return($x['permissions']); + + } + + static public function BlockedAnonPerms() { + + // Perms from the above list that are blocked from anonymous observers. + // e.g. you must be authenticated. + + $res = array(); + $perms = PermissionLimits::Std_limits(); + foreach($perms as $perm => $limit) { + if($limit != PERMS_PUBLIC) { + $res[] = $perm; + } + } + + $x = array('permissions' => $res); + call_hooks('write_perms',$x); + return($x['permissions']); + + } + + // converts [ 0 => 'view_stream', ... ] + // to [ 'view_stream' => 1 ] + // for any permissions in $arr; + // Undeclared permissions are set to 0 + + static public function FilledPerms($arr) { + $everything = self::Perms(); + $ret = []; + foreach($everything as $k => $v) { + if(in_array($k,$arr)) + $ret[$k] = 1; + else + $ret[$k] = 0; + } + return $ret; + + } + + static public function FilledAutoperms($channel_id) { + if(! intval(get_pconfig($channel_id,'system','autoperms'))) + return false; + + $arr = []; + $r = q("select * from pconfig where uid = %d and cat = 'autoperms'", + intval($channel_id) + ); + if($r) { + foreach($r as $rr) { + $arr[$rr['k']] = $arr[$rr['v']]; + } + } + return $arr; + } + + static public function PermsCompare($p1,$p2) { + foreach($p1 as $k => $v) { + if(! array_key_exists($k,$p2)) + return false; + if($p1[$k] != $p2[$k]) + return false; + } + return true; + } +} \ No newline at end of file diff --git a/Zotlabs/Daemon/Checksites.php b/Zotlabs/Daemon/Checksites.php new file mode 100644 index 000000000..991456319 --- /dev/null +++ b/Zotlabs/Daemon/Checksites.php @@ -0,0 +1,55 @@ + 1) && ($argv[1])) + $site_id = $argv[1]; + + if($site_id) + $sql_options = " and site_url = '" . dbesc($argv[1]) . "' "; + + $days = intval(get_config('system','sitecheckdays')); + if($days < 1) + $days = 30; + + $r = q("select * from site where site_dead = 0 and site_update < %s - INTERVAL %s and site_type = %d $sql_options ", + db_utcnow(), db_quoteinterval($days . ' DAY'), + intval(SITE_TYPE_ZOT) + ); + + if(! $r) + return; + + foreach($r as $rr) { + if(! strcasecmp($rr['site_url'],z_root())) + continue; + + $x = ping_site($rr['site_url']); + if($x['success']) { + logger('checksites: ' . $rr['site_url']); + q("update site set site_update = '%s' where site_url = '%s' ", + dbesc(datetime_convert()), + dbesc($rr['site_url']) + ); + } + else { + logger('marking dead site: ' . $x['message']); + q("update site set site_dead = 1 where site_url = '%s' ", + dbesc($rr['site_url']) + ); + } + } + + return; + } +} diff --git a/Zotlabs/Daemon/Cli_suggest.php b/Zotlabs/Daemon/Cli_suggest.php new file mode 100644 index 000000000..5dced462d --- /dev/null +++ b/Zotlabs/Daemon/Cli_suggest.php @@ -0,0 +1,14 @@ + $maxsysload) { + logger('system: load ' . $load . ' too high. Cron deferred to next scheduled run.'); + return; + } + } + + // Check for a lockfile. If it exists, but is over an hour old, it's stale. Ignore it. + $lockfile = 'store/[data]/cron'; + if((file_exists($lockfile)) && (filemtime($lockfile) > (time() - 3600)) + && (! get_config('system','override_cron_lockfile'))) { + logger("cron: Already running"); + return; + } + + // Create a lockfile. Needs two vars, but $x doesn't need to contain anything. + file_put_contents($lockfile, $x); + + logger('cron: start'); + + // run queue delivery process in the background + + Master::Summon(array('Queue')); + + Master::Summon(array('Poller')); + + // maintenance for mod sharedwithme - check for updated items and remove them + + require_once('include/sharedwithme.php'); + apply_updates(); + + // expire any expired mail + + q("delete from mail where expires != '%s' and expires < %s ", + dbesc(NULL_DATE), + db_utcnow() + ); + + // expire any expired items + + $r = q("select id from item where expires != '%s' and expires < %s + and item_deleted = 0 ", + dbesc(NULL_DATE), + db_utcnow() + ); + if($r) { + require_once('include/items.php'); + foreach($r as $rr) + drop_item($rr['id'],false); + } + + + // delete expired access tokens + + q("delete from atoken where atoken_expires != '%s' && atoken_expires < %s", + dbesc(NULL_DATE), + db_utcnow() + ); + + + + // Ensure that every channel pings a directory server once a month. This way we can discover + // channels and sites that quietly vanished and prevent the directory from accumulating stale + // or dead entries. + + $r = q("select channel_id from channel where channel_dirdate < %s - INTERVAL %s", + db_utcnow(), + db_quoteinterval('30 DAY') + ); + if($r) { + foreach($r as $rr) { + Master::Summon(array('Directory',$rr['channel_id'],'force')); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + } + + // publish any applicable items that were set to be published in the future + // (time travel posts). Restrict to items that have come of age in the last + // couple of days to limit the query to something reasonable. + + $r = q("select id from item where item_delayed = 1 and created <= %s and created > '%s' ", + db_utcnow(), + dbesc(datetime_convert('UTC','UTC','now - 2 days')) + ); + if($r) { + foreach($r as $rr) { + $x = q("update item set item_delayed = 0 where id = %d", + intval($rr['id']) + ); + if($x) { + $z = q("select * from item where id = %d", + intval($message_id) + ); + if($z) { + xchan_query($z); + $sync_item = fetch_post_tags($z); + build_sync_packet($sync_item[0]['uid'], + [ + 'item' => [ encode_item($sync_item[0],true) ] + ] + ); + } + Master::Summon(array('Notifier','wall-new',$rr['id'])); + } + } + } + + $abandon_days = intval(get_config('system','account_abandon_days')); + if($abandon_days < 1) + $abandon_days = 0; + + + // once daily run birthday_updates and then expire in background + + // FIXME: add birthday updates, both locally and for xprof for use + // by directory servers + + $d1 = intval(get_config('system','last_expire_day')); + $d2 = intval(datetime_convert('UTC','UTC','now','d')); + + // Allow somebody to staggger daily activities if they have more than one site on their server, + // or if it happens at an inconvenient (busy) hour. + + $h1 = intval(get_config('system','cron_hour')); + $h2 = intval(datetime_convert('UTC','UTC','now','G')); + + + if(($d2 != $d1) && ($h1 == $h2)) { + Master::Summon(array('Cron_daily')); + } + + // update any photos which didn't get imported properly + // This should be rare + + $r = q("select xchan_photo_l, xchan_hash from xchan where xchan_photo_l != '' and xchan_photo_m = '' + and xchan_photo_date < %s - INTERVAL %s", + db_utcnow(), + db_quoteinterval('1 DAY') + ); + if($r) { + require_once('include/photo/photo_driver.php'); + foreach($r as $rr) { + $photos = import_xchan_photo($rr['xchan_photo_l'],$rr['xchan_hash']); + $x = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' + where xchan_hash = '%s'", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($rr['xchan_hash']) + ); + } + } + + + // pull in some public posts + + if(! get_config('system','disable_discover_tab')) + Master::Summon(array('Externals')); + + $generation = 0; + + $restart = false; + + if(($argc > 1) && ($argv[1] == 'restart')) { + $restart = true; + $generation = intval($argv[2]); + if(! $generation) + killme(); + } + + reload_plugins(); + + $d = datetime_convert(); + + // TODO check to see if there are any cronhooks before wasting a process + + if(! $restart) + Master::Summon(array('Cronhooks')); + + set_config('system','lastcron',datetime_convert()); + + //All done - clear the lockfile + @unlink($lockfile); + + return; + } +} diff --git a/Zotlabs/Daemon/Cron_daily.php b/Zotlabs/Daemon/Cron_daily.php new file mode 100644 index 000000000..a16d49853 --- /dev/null +++ b/Zotlabs/Daemon/Cron_daily.php @@ -0,0 +1,90 @@ +start(); + + $_SESSION['authenticated'] = 1; + $_SESSION['uid'] = $argv[1]; + + $x = session_id(); + + $f = 'store/[data]/cookie_' . $argv[1]; + $c = 'store/[data]/cookien_' . $argv[1]; + + $e = file_exists($f); + + $output = ''; + + if($e) { + $lines = file($f); + if($lines) { + foreach($lines as $line) { + if(strlen($line) > 0 && $line[0] != '#' && substr_count($line, "\t") == 6) { + $tokens = explode("\t", $line); + $tokens = array_map('trim', $tokens); + if($tokens[4] > time()) { + $output .= $line . "\n"; + } + } + else + $output .= $line; + } + } + } + $t = time() + (24 * 3600); + file_put_contents($f, $output . 'HttpOnly_' . \App::get_hostname() . "\tFALSE\t/\tTRUE\t$t\tPHPSESSID\t" . $x, (($e) ? FILE_APPEND : 0)); + + file_put_contents($c,$x); + + killme(); + } +} \ No newline at end of file diff --git a/Zotlabs/Daemon/Deliver.php b/Zotlabs/Daemon/Deliver.php new file mode 100644 index 000000000..dbc311cf5 --- /dev/null +++ b/Zotlabs/Daemon/Deliver.php @@ -0,0 +1,85 @@ + json_encode(array('success' => true, 'pickup' => array(array('notify' => $notify,'message' => $mm))))); + zot_import($msg,z_root()); + } + } + else { + $msg = array('body' => json_encode(array('success' => true, 'pickup' => array(array('notify' => $notify,'message' => $m))))); + $dresult = zot_import($msg,z_root()); + } + + remove_queue_item($r[0]['outq_hash']); + + if($dresult && is_array($dresult)) { + foreach($dresult as $xx) { + if(is_array($xx) && array_key_exists('message_id',$xx)) { + if(delivery_report_is_storable($xx)) { + q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s','%s','%s','%s','%s' ) ", + dbesc($xx['message_id']), + dbesc($xx['location']), + dbesc($xx['recipient']), + dbesc($xx['status']), + dbesc(datetime_convert($xx['date'])), + dbesc($xx['sender']) + ); + } + } + } + } + + q("delete from dreport where dreport_queue = '%s'", + dbesc($argv[$x]) + ); + } + } + + // otherwise it's a remote delivery - call queue_deliver() with the $immediate flag + + queue_deliver($r[0],true); + + } + } + } +} diff --git a/Zotlabs/Daemon/Deliver_hooks.php b/Zotlabs/Daemon/Deliver_hooks.php new file mode 100644 index 000000000..e8b5acef0 --- /dev/null +++ b/Zotlabs/Daemon/Deliver_hooks.php @@ -0,0 +1,24 @@ + 2) { + if($argv[2] === 'force') + $force = true; + if($argv[2] === 'nopush') + $pushall = false; + } + + logger('directory update', LOGGER_DEBUG); + + $dirmode = get_config('system','directory_mode'); + if($dirmode === false) + $dirmode = DIRECTORY_MODE_NORMAL; + + $x = q("select * from channel where channel_id = %d limit 1", + intval($argv[1]) + ); + if(! $x) + return; + + $channel = $x[0]; + + if($dirmode != DIRECTORY_MODE_NORMAL) { + + // this is an in-memory update and we don't need to send a network packet. + + local_dir_update($argv[1],$force); + + q("update channel set channel_dirdate = '%s' where channel_id = %d", + dbesc(datetime_convert()), + intval($channel['channel_id']) + ); + + // Now update all the connections + if($pushall) + Master::Summon(array('Notifier','refresh_all',$channel['channel_id'])); + + return; + } + + // otherwise send the changes upstream + + $directory = find_upstream_directory($dirmode); + $url = $directory['url'] . '/post'; + + // ensure the upstream directory is updated + + $packet = zot_build_packet($channel,(($force) ? 'force_refresh' : 'refresh')); + $z = zot_zot($url,$packet); + + // re-queue if unsuccessful + + if(! $z['success']) { + + /** @FIXME we aren't updating channel_dirdate if we have to queue + * the directory packet. That means we'll try again on the next poll run. + */ + + $hash = random_string(); + + queue_insert(array( + 'hash' => $hash, + 'account_id' => $channel['channel_account_id'], + 'channel_id' => $channel['channel_id'], + 'posturl' => $url, + 'notify' => $packet, + )); + + } + else { + q("update channel set channel_dirdate = '%s' where channel_id = %d", + dbesc(datetime_convert()), + intval($channel['channel_id']) + ); + } + + // Now update all the connections + if($pushall) + Master::Summon(array('Notifier','refresh_all',$channel['channel_id'])); + + } +} diff --git a/Zotlabs/Daemon/Expire.php b/Zotlabs/Daemon/Expire.php new file mode 100644 index 000000000..215513e87 --- /dev/null +++ b/Zotlabs/Daemon/Expire.php @@ -0,0 +1,93 @@ + ''); + call_hooks('externals_url_select',$arr); + + if($arr['url']) { + $url = $arr['url']; + } + else { + $randfunc = db_getfunc('RAND'); + + // fixme this query does not deal with directory realms. + + $r = q("select site_url, site_pull from site where site_url != '%s' and site_flags != %d and site_type = %d and site_dead = 0 order by $randfunc limit 1", + dbesc(z_root()), + intval(DIRECTORY_MODE_STANDALONE), + intval(SITE_TYPE_ZOT) + ); + if($r) + $url = $r[0]['site_url']; + } + + $blacklisted = false; + + if(! check_siteallowed($url)) { + logger('blacklisted site: ' . $url); + $blacklisted = true; + } + + $attempts ++; + + // make sure we can eventually break out if somebody blacklists all known sites + + if($blacklisted) { + if($attempts > 20) + break; + $attempts --; + continue; + } + + if($url) { + if($r[0]['site_pull'] !== NULL_DATE) + $mindate = urlencode(datetime_convert('','',$r[0]['site_pull'] . ' - 1 day')); + else { + $days = get_config('externals','since_days'); + if($days === false) + $days = 15; + $mindate = urlencode(datetime_convert('','','now - ' . intval($days) . ' days')); + } + + $feedurl = $url . '/zotfeed?f=&mindate=' . $mindate; + + logger('externals: pulling public content from ' . $feedurl, LOGGER_DEBUG); + + $x = z_fetch_url($feedurl); + if(($x) && ($x['success'])) { + + q("update site set site_pull = '%s' where site_url = '%s'", + dbesc(datetime_convert()), + dbesc($url) + ); + + $j = json_decode($x['body'],true); + if($j['success'] && $j['messages']) { + $sys = get_sys_channel(); + foreach($j['messages'] as $message) { + // on these posts, clear any route info. + $message['route'] = ''; + $results = process_delivery(array('hash' => 'undefined'), get_item_elements($message), + array(array('hash' => $sys['xchan_hash'])), false, true); + $total ++; + } + logger('externals: import_public_posts: ' . $total . ' messages imported', LOGGER_DEBUG); + } + } + } + } + } +} diff --git a/Zotlabs/Daemon/Gprobe.php b/Zotlabs/Daemon/Gprobe.php new file mode 100644 index 000000000..43cce93c3 --- /dev/null +++ b/Zotlabs/Daemon/Gprobe.php @@ -0,0 +1,33 @@ + 3) ? $argv[3] : null); + + if(! $item_id) + return; + + $sys = get_sys_channel(); + + $deliveries = array(); + + $dead_hubs = array(); + + $dh = q("select site_url from site where site_dead = 1"); + if($dh) { + foreach($dh as $dead) { + $dead_hubs[] = $dead['site_url']; + } + } + + + $request = false; + $mail = false; + $top_level = false; + $location = false; + $recipients = array(); + $url_recipients = array(); + $normal_mode = true; + $packet_type = 'undefined'; + + if($cmd === 'mail' || $cmd === 'single_mail') { + $normal_mode = false; + $mail = true; + $private = true; + $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", + intval($item_id) + ); + if(! $message) { + return; + } + xchan_mail_query($message[0]); + $uid = $message[0]['channel_id']; + $recipients[] = $message[0]['from_xchan']; // include clones + $recipients[] = $message[0]['to_xchan']; + $item = $message[0]; + + $encoded_item = encode_mail($item); + + $s = q("select * from channel where channel_id = %d limit 1", + intval($item['channel_id']) + ); + if($s) + $channel = $s[0]; + + } + elseif($cmd === 'request') { + $channel_id = $item_id; + $xchan = $argv[3]; + $request_message_id = $argv[4]; + + $s = q("select * from channel where channel_id = %d limit 1", + intval($channel_id) + ); + if($s) + $channel = $s[0]; + + $private = true; + $recipients[] = $xchan; + $packet_type = 'request'; + $normal_mode = false; + } + elseif($cmd == 'permission_update' || $cmd == 'permission_create') { + // Get the (single) recipient + $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d and abook_self = 0", + intval($item_id) + ); + if($r) { + $uid = $r[0]['abook_channel']; + // Get the sender + $channel = channelx_by_n($uid); + if($channel) { + $perm_update = array('sender' => $channel, 'recipient' => $r[0], 'success' => false, 'deliveries' => ''); + + if($cmd == 'permission_create') + call_hooks('permissions_create',$perm_update); + else + call_hooks('permissions_update',$perm_update); + + if($perm_update['success']) { + if($perm_update['deliveries']) { + $deliveries[] = $perm_update['deliveries']; + do_delivery($deliveries); + } + return; + } + else { + $recipients[] = $r[0]['abook_xchan']; + $private = false; + $packet_type = 'refresh'; + $packet_recips = array(array('guid' => $r[0]['xchan_guid'],'guid_sig' => $r[0]['xchan_guid_sig'],'hash' => $r[0]['xchan_hash'])); + } + } + } + } + elseif($cmd === 'refresh_all') { + logger('notifier: refresh_all: ' . $item_id); + $uid = $item_id; + $channel = channelx_by_n($item_id); + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + $private = false; + $packet_type = 'refresh'; + } + elseif($cmd === 'location') { + logger('notifier: location: ' . $item_id); + $s = q("select * from channel where channel_id = %d limit 1", + intval($item_id) + ); + if($s) + $channel = $s[0]; + $uid = $item_id; + $recipients = array(); + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + + $encoded_item = array('locations' => zot_encode_locations($channel),'type' => 'location', 'encoding' => 'zot'); + $target_item = array('aid' => $channel['channel_account_id'],'uid' => $channel['channel_id']); + $private = false; + $packet_type = 'location'; + $location = true; + } + elseif($cmd === 'purge_all') { + logger('notifier: purge_all: ' . $item_id); + $s = q("select * from channel where channel_id = %d limit 1", + intval($item_id) + ); + if($s) + $channel = $s[0]; + $uid = $item_id; + $recipients = array(); + $r = q("select abook_xchan from abook where abook_channel = %d", + intval($item_id) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } + } + $private = false; + $packet_type = 'purge'; + } + else { + + // Normal items + + // Fetch the target item + + $r = q("SELECT * FROM item WHERE id = %d and parent != 0 LIMIT 1", + intval($item_id) + ); + + if(! $r) + return; + + xchan_query($r); + + $r = fetch_post_tags($r); + + $target_item = $r[0]; + $deleted_item = false; + + if(intval($target_item['item_deleted'])) { + logger('notifier: target item ITEM_DELETED', LOGGER_DEBUG); + $deleted_item = true; + } + + if(intval($target_item['item_type']) != ITEM_TYPE_POST) { + logger('notifier: target item not forwardable: type ' . $target_item['item_type'], LOGGER_DEBUG); + return; + } + + // Check for non published items, but allow an exclusion for transmitting hidden file activities + + if(intval($target_item['item_unpublished']) || intval($target_item['item_delayed']) || + ( intval($target_item['item_hidden']) && ($target_item['obj_type'] !== ACTIVITY_OBJ_FILE))) { + logger('notifier: target item not published, so not forwardable', LOGGER_DEBUG); + return; + } + + if(strpos($target_item['postopts'],'nodeliver') !== false) { + logger('notifier: target item is undeliverable', LOGGER_DEBUG); + return; + } + + $s = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", + intval($target_item['uid']) + ); + if($s) + $channel = $s[0]; + + if($channel['channel_hash'] !== $target_item['author_xchan'] && $channel['channel_hash'] !== $target_item['owner_xchan']) { + logger("notifier: Sending channel {$channel['channel_hash']} is not owner {$target_item['owner_xchan']} or author {$target_item['author_xchan']}", LOGGER_NORMAL, LOG_WARNING); + return; + } + + + if($target_item['id'] == $target_item['parent']) { + $parent_item = $target_item; + $top_level_post = true; + } + else { + // fetch the parent item + $r = q("SELECT * from item where id = %d order by id asc", + intval($target_item['parent']) + ); + + if(! $r) + return; + + if(strpos($r[0]['postopts'],'nodeliver') !== false) { + logger('notifier: target item is undeliverable', LOGGER_DEBUG, LOG_NOTICE); + return; + } + + xchan_query($r); + $r = fetch_post_tags($r); + + $parent_item = $r[0]; + $top_level_post = false; + } + + // avoid looping of discover items 12/4/2014 + + if($sys && $parent_item['uid'] == $sys['channel_id']) + return; + + $encoded_item = encode_item($target_item); + + // Send comments to the owner to re-deliver to everybody in the conversation + // We only do this if the item in question originated on this site. This prevents looping. + // To clarify, a site accepting a new comment is responsible for sending it to the owner for relay. + // Relaying should never be initiated on a post that arrived from elsewhere. + + // We should normally be able to rely on ITEM_ORIGIN, but start_delivery_chain() incorrectly set this + // flag on comments for an extended period. So we'll also call comment_local_origin() which looks at + // the hostname in the message_id and provides a second (fallback) opinion. + + $relay_to_owner = (((! $top_level_post) && (intval($target_item['item_origin'])) && comment_local_origin($target_item)) ? true : false); + + + + $uplink = false; + + // $cmd === 'relay' indicates the owner is sending it to the original recipients + // don't allow the item in the relay command to relay to owner under any circumstances, it will loop + + logger('notifier: relay_to_owner: ' . (($relay_to_owner) ? 'true' : 'false'), LOGGER_DATA, LOG_DEBUG); + logger('notifier: top_level_post: ' . (($top_level_post) ? 'true' : 'false'), LOGGER_DATA, LOG_DEBUG); + + // tag_deliver'd post which needs to be sent back to the original author + + if(($cmd === 'uplink') && intval($parent_item['item_uplink']) && (! $top_level_post)) { + logger('notifier: uplink'); + $uplink = true; + } + + if(($relay_to_owner || $uplink) && ($cmd !== 'relay')) { + logger('notifier: followup relay', LOGGER_DEBUG); + $recipients = array(($uplink) ? $parent_item['source_xchan'] : $parent_item['owner_xchan']); + $private = true; + if(! $encoded_item['flags']) + $encoded_item['flags'] = array(); + $encoded_item['flags'][] = 'relay'; + } + else { + logger('notifier: normal distribution', LOGGER_DEBUG); + if($cmd === 'relay') + logger('notifier: owner relay'); + + // if our parent is a tag_delivery recipient, uplink to the original author causing + // a delivery fork. + + if(($parent_item) && intval($parent_item['item_uplink']) && (! $top_level_post) && ($cmd !== 'uplink')) { + // don't uplink a relayed post to the relay owner + if($parent_item['source_xchan'] !== $parent_item['owner_xchan']) { + logger('notifier: uplinking this item'); + Master::Summon(array('Notifier','uplink',$item_id)); + } + } + + $private = false; + $recipients = collect_recipients($parent_item,$private); + + // FIXME add any additional recipients such as mentions, etc. + + // don't send deletions onward for other people's stuff + // TODO verify this is needed - copied logic from same place in old code + + if(intval($target_item['item_deleted']) && (! intval($target_item['item_wall']))) { + logger('notifier: ignoring delete notification for non-wall item', LOGGER_NORMAL, LOG_NOTICE); + return; + } + } + + } + + $walltowall = (($top_level_post && $channel['xchan_hash'] === $target_item['author_xchan']) ? true : false); + + // Generic delivery section, we have an encoded item and recipients + // Now start the delivery process + + $x = $encoded_item; + $x['title'] = 'private'; + $x['body'] = 'private'; + logger('notifier: encoded item: ' . print_r($x,true), LOGGER_DATA, LOG_DEBUG); + + stringify_array_elms($recipients); + if(! $recipients) + return; + +// logger('notifier: recipients: ' . print_r($recipients,true), LOGGER_NORMAL, LOG_DEBUG); + + $env_recips = (($private) ? array() : null); + + $details = q("select xchan_hash, xchan_instance_url, xchan_network, xchan_addr, xchan_guid, xchan_guid_sig from xchan where xchan_hash in (" . implode(',',$recipients) . ")"); + + + $recip_list = array(); + + if($details) { + foreach($details as $d) { + + $recip_list[] = $d['xchan_addr'] . ' (' . $d['xchan_hash'] . ')'; + if($private) + $env_recips[] = array('guid' => $d['xchan_guid'],'guid_sig' => $d['xchan_guid_sig'],'hash' => $d['xchan_hash']); + + if($d['xchan_network'] === 'mail' && $normal_mode) { + $delivery_options = get_xconfig($d['xchan_hash'],'system','delivery_mode'); + if(! $delivery_options) + format_and_send_email($channel,$d,$target_item); + } + } + } + + + $narr = array( + 'channel' => $channel, + 'env_recips' => $env_recips, + 'packet_recips' => $packet_recips, + 'recipients' => $recipients, + 'item' => $item, + 'target_item' => $target_item, + 'top_level_post' => $top_level_post, + 'private' => $private, + 'relay_to_owner' => $relay_to_owner, + 'uplink' => $uplink, + 'cmd' => $cmd, + 'mail' => $mail, + 'single' => (($cmd === 'single_mail' || $cmd === 'single_activity') ? true : false), + 'location' => $location, + 'request' => $request, + 'normal_mode' => $normal_mode, + 'packet_type' => $packet_type, + 'walltowall' => $walltowall, + 'queued' => array() + ); + + call_hooks('notifier_process', $narr); + if($narr['queued']) { + foreach($narr['queued'] as $pq) + $deliveries[] = $pq; + } + + // notifier_process can alter the recipient list + + $recipients = $narr['recipients']; + $env_recips = $narr['env_recips']; + $packet_recips = $narr['packet_recips']; + + if(($private) && (! $env_recips)) { + // shouldn't happen + logger('notifier: private message with no envelope recipients.' . print_r($argv,true), LOGGER_NORMAL, LOG_NOTICE); + } + + logger('notifier: recipients (may be delivered to more if public): ' . print_r($recip_list,true), LOGGER_DEBUG); + + + // Now we have collected recipients (except for external mentions, FIXME) + // Let's reduce this to a set of hubs. + + $r = q("select * from hubloc where hubloc_hash in (" . implode(',',$recipients) . ") + and hubloc_error = 0 and hubloc_deleted = 0" + ); + + + if(! $r) { + logger('notifier: no hubs', LOGGER_NORMAL, LOG_NOTICE); + return; + } + + $hubs = $r; + + + + /** + * Reduce the hubs to those that are unique. For zot hubs, we need to verify uniqueness by the sitekey, since it may have been + * a re-install which has not yet been detected and pruned. + * For other networks which don't have or require sitekeys, we'll have to use the URL + */ + + + $hublist = array(); // this provides an easily printable list for the logs + $dhubs = array(); // delivery hubs where we store our resulting unique array + $keys = array(); // array of keys to check uniquness for zot hubs + $urls = array(); // array of urls to check uniqueness of hubs from other networks + + + foreach($hubs as $hub) { + if(in_array($hub['hubloc_url'],$dead_hubs)) { + logger('skipping dead hub: ' . $hub['hubloc_url'], LOGGER_DEBUG, LOG_INFO); + continue; + } + + if($hub['hubloc_network'] == 'zot') { + if(! in_array($hub['hubloc_sitekey'],$keys)) { + $hublist[] = $hub['hubloc_host']; + $dhubs[] = $hub; + $keys[] = $hub['hubloc_sitekey']; + } + } + else { + if(! in_array($hub['hubloc_url'],$urls)) { + $hublist[] = $hub['hubloc_host']; + $dhubs[] = $hub; + $urls[] = $hub['hubloc_url']; + } + } + } + + logger('notifier: will notify/deliver to these hubs: ' . print_r($hublist,true), LOGGER_DEBUG, LOG_DEBUG); + + + foreach($dhubs as $hub) { + + if($hub['hubloc_network'] !== 'zot') { + + $narr = array( + 'channel' => $channel, + 'env_recips' => $env_recips, + 'packet_recips' => $packet_recips, + 'recipients' => $recipients, + 'item' => $item, + 'target_item' => $target_item, + 'hub' => $hub, + 'top_level_post' => $top_level_post, + 'private' => $private, + 'relay_to_owner' => $relay_to_owner, + 'uplink' => $uplink, + 'cmd' => $cmd, + 'mail' => $mail, + 'single' => (($cmd === 'single_mail' || $cmd === 'single_activity') ? true : false), + 'location' => $location, + 'request' => $request, + 'normal_mode' => $normal_mode, + 'packet_type' => $packet_type, + 'walltowall' => $walltowall, + 'queued' => array() + ); + + + call_hooks('notifier_hub',$narr); + if($narr['queued']) { + foreach($narr['queued'] as $pq) + $deliveries[] = $pq; + } + continue; + + } + + // singleton deliveries by definition 'not got zot'. + // Single deliveries are other federated networks (plugins) and we're essentially + // delivering only to those that have this site url in their abook_instance + // and only from within a sync operation. This means if you post from a clone, + // and a connection is connected to one of your other clones; assuming that hub + // is running it will receive a sync packet. On receipt of this sync packet it + // will invoke a delivery to those connections which are connected to just that + // hub instance. + + if($cmd === 'single_mail' || $cmd === 'single_activity') { + continue; + } + + // default: zot protocol + + $hash = random_string(); + $packet = null; + + if($packet_type === 'refresh' || $packet_type === 'purge') { + $packet = zot_build_packet($channel,$packet_type,(($packet_recips) ? $packet_recips : null)); + } + elseif($packet_type === 'request') { + $packet = zot_build_packet($channel,$packet_type,$env_recips,$hub['hubloc_sitekey'],$hash, + array('message_id' => $request_message_id) + ); + } + + if($packet) { + queue_insert(array( + 'hash' => $hash, + 'account_id' => $channel['channel_account_id'], + 'channel_id' => $channel['channel_id'], + 'posturl' => $hub['hubloc_callback'], + 'notify' => $packet + )); + } + else { + $packet = zot_build_packet($channel,'notify',$env_recips,(($private) ? $hub['hubloc_sitekey'] : null),$hash); + queue_insert(array( + 'hash' => $hash, + 'account_id' => $target_item['aid'], + 'channel_id' => $target_item['uid'], + 'posturl' => $hub['hubloc_callback'], + 'notify' => $packet, + 'msg' => json_encode($encoded_item) + )); + + // only create delivery reports for normal undeleted items + if(is_array($target_item) && array_key_exists('postopts',$target_item) && (! $target_item['item_deleted']) && (! get_config('system','disable_dreport'))) { + q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_result, dreport_time, dreport_xchan, dreport_queue ) values ( '%s','%s','%s','%s','%s','%s','%s' ) ", + dbesc($target_item['mid']), + dbesc($hub['hubloc_host']), + dbesc($hub['hubloc_host']), + dbesc('queued'), + dbesc(datetime_convert()), + dbesc($channel['channel_hash']), + dbesc($hash) + ); + } + } + + $deliveries[] = $hash; + } + + if($normal_mode) { + $x = q("select * from hook where hook = 'notifier_normal'"); + if($x) + Master::Summon(array('Deliver_hooks',$target_item['id'])); + + } + + if($deliveries) + do_delivery($deliveries); + + logger('notifier: basic loop complete.', LOGGER_DEBUG); + + call_hooks('notifier_end',$target_item); + + logger('notifer: complete.'); + return; + + } +} + diff --git a/Zotlabs/Daemon/Onedirsync.php b/Zotlabs/Daemon/Onedirsync.php new file mode 100644 index 000000000..cc16c0b58 --- /dev/null +++ b/Zotlabs/Daemon/Onedirsync.php @@ -0,0 +1,76 @@ + 1) && (intval($argv[1]))) + $update_id = intval($argv[1]); + + if(! $update_id) { + logger('onedirsync: no update'); + return; + } + + $r = q("select * from updates where ud_id = %d limit 1", + intval($update_id) + ); + + if(! $r) + return; + if(($r[0]['ud_flags'] & UPDATE_FLAGS_UPDATED) || (! $r[0]['ud_addr'])) + return; + + // Have we probed this channel more recently than the other directory server + // (where we received this update from) ? + // If we have, we don't need to do anything except mark any older entries updated + + $x = q("select * from updates where ud_addr = '%s' and ud_date > '%s' and ( ud_flags & %d )>0 order by ud_date desc limit 1", + dbesc($r[0]['ud_addr']), + dbesc($r[0]['ud_date']), + intval(UPDATE_FLAGS_UPDATED) + ); + if($x) { + $y = q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and ( ud_flags & %d ) = 0 and ud_date != '%s'", + intval(UPDATE_FLAGS_UPDATED), + dbesc($r[0]['ud_addr']), + intval(UPDATE_FLAGS_UPDATED), + dbesc($x[0]['ud_date']) + ); + return; + } + + // ignore doing an update if this ud_addr refers to a known dead hubloc + + $h = q("select * from hubloc where hubloc_addr = '%s' limit 1", + dbesc($r[0]['ud_addr']) + ); + if(($h) && ($h[0]['hubloc_status'] & HUBLOC_OFFLINE)) { + $y = q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and ( ud_flags & %d ) = 0 ", + intval(UPDATE_FLAGS_UPDATED), + dbesc($r[0]['ud_addr']), + intval(UPDATE_FLAGS_UPDATED) + ); + + return; + } + + // we might have to pull this out some day, but for now update_directory_entry() + // runs zot_finger() and is kind of zot specific + + if($h && $h[0]['hubloc_network'] !== 'zot') + return; + + update_directory_entry($r[0]); + + return; + } +} diff --git a/Zotlabs/Daemon/Onepoll.php b/Zotlabs/Daemon/Onepoll.php new file mode 100644 index 000000000..21c46cec5 --- /dev/null +++ b/Zotlabs/Daemon/Onepoll.php @@ -0,0 +1,154 @@ + 1) && (intval($argv[1]))) + $contact_id = intval($argv[1]); + + if(! $contact_id) { + logger('onepoll: no contact'); + return; + } + + $d = datetime_convert(); + + $contacts = q("SELECT abook.*, xchan.*, account.* + FROM abook LEFT JOIN account on abook_account = account_id left join xchan on xchan_hash = abook_xchan + where abook_id = %d + and abook_pending = 0 and abook_archived = 0 and abook_blocked = 0 and abook_ignored = 0 + AND (( account_flags = %d ) OR ( account_flags = %d )) limit 1", + intval($contact_id), + intval(ACCOUNT_OK), + intval(ACCOUNT_UNVERIFIED) + ); + + if(! $contacts) { + logger('onepoll: abook_id not found: ' . $contact_id); + return; + } + + $contact = $contacts[0]; + + $t = $contact['abook_updated']; + + $importer_uid = $contact['abook_channel']; + + $r = q("SELECT * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", + intval($importer_uid) + ); + + if(! $r) + return; + + $importer = $r[0]; + + logger("onepoll: poll: ({$contact['id']}) IMPORTER: {$importer['xchan_name']}, CONTACT: {$contact['xchan_name']}"); + + $last_update = ((($contact['abook_updated'] === $contact['abook_created']) || ($contact['abook_updated'] === NULL_DATE)) + ? datetime_convert('UTC','UTC','now - 7 days') + : datetime_convert('UTC','UTC',$contact['abook_updated'] . ' - 2 days') + ); + + if($contact['xchan_network'] === 'rss') { + logger('onepoll: processing feed ' . $contact['xchan_name'], LOGGER_DEBUG); + handle_feed($importer['channel_id'],$contact_id,$contact['xchan_hash']); + q("update abook set abook_connected = '%s' where abook_id = %d", + dbesc(datetime_convert()), + intval($contact['abook_id']) + ); + return; + } + + if($contact['xchan_network'] !== 'zot') + return; + + // update permissions + + $x = zot_refresh($contact,$importer); + + $responded = false; + $updated = datetime_convert(); + $connected = datetime_convert(); + if(! $x) { + // mark for death by not updating abook_connected, this is caught in include/poller.php + q("update abook set abook_updated = '%s' where abook_id = %d", + dbesc($updated), + intval($contact['abook_id']) + ); + } + else { + q("update abook set abook_updated = '%s', abook_connected = '%s' where abook_id = %d", + dbesc($updated), + dbesc($connected), + intval($contact['abook_id']) + ); + $responded = true; + } + + if(! $responded) + return; + + if($contact['xchan_connurl']) { + $fetch_feed = true; + $x = null; + + $can_view_stream = intval(get_abconfig($importer_uid,$contact['abook_xchan'],'their_perms','view_stream')); + + if(! $can_view_stream) + $fetch_feed = false; + + if($fetch_feed) { + + $feedurl = str_replace('/poco/','/zotfeed/',$contact['xchan_connurl']); + $feedurl .= '?f=&mindate=' . urlencode($last_update); + + $x = z_fetch_url($feedurl); + + logger('feed_update: ' . print_r($x,true), LOGGER_DATA); + + } + + if(($x) && ($x['success'])) { + $total = 0; + logger('onepoll: feed update ' . $contact['xchan_name'] . ' ' . $feedurl); + + $j = json_decode($x['body'],true); + if($j['success'] && $j['messages']) { + foreach($j['messages'] as $message) { + $results = process_delivery(array('hash' => $contact['xchan_hash']), get_item_elements($message), + array(array('hash' => $importer['xchan_hash'])), false); + logger('onepoll: feed_update: process_delivery: ' . print_r($results,true), LOGGER_DATA); + $total ++; + } + logger("onepoll: $total messages processed"); + } + } + } + + + // update the poco details for this connection + + if($contact['xchan_connurl']) { + $r = q("SELECT xlink_id from xlink + where xlink_xchan = '%s' and xlink_updated > %s - INTERVAL %s and xlink_static = 0 limit 1", + intval($contact['xchan_hash']), + db_utcnow(), db_quoteinterval('1 DAY') + ); + if(! $r) { + poco_load($contact['xchan_hash'],$contact['xchan_connurl']); + } + } + + return; + } +} diff --git a/Zotlabs/Daemon/Poller.php b/Zotlabs/Daemon/Poller.php new file mode 100644 index 000000000..75efbf8f7 --- /dev/null +++ b/Zotlabs/Daemon/Poller.php @@ -0,0 +1,202 @@ + $maxsysload) { + logger('system: load ' . $load . ' too high. Poller deferred to next scheduled run.'); + return; + } + } + + $interval = intval(get_config('system','poll_interval')); + if(! $interval) + $interval = ((get_config('system','delivery_interval') === false) ? 3 : intval(get_config('system','delivery_interval'))); + + // Check for a lockfile. If it exists, but is over an hour old, it's stale. Ignore it. + $lockfile = 'store/[data]/poller'; + if((file_exists($lockfile)) && (filemtime($lockfile) > (time() - 3600)) + && (! get_config('system','override_poll_lockfile'))) { + logger("poller: Already running"); + return; + } + + // Create a lockfile. Needs two vars, but $x doesn't need to contain anything. + file_put_contents($lockfile, $x); + + logger('poller: start'); + + $manual_id = 0; + $generation = 0; + + $force = false; + $restart = false; + + if(($argc > 1) && ($argv[1] == 'force')) + $force = true; + + if(($argc > 1) && ($argv[1] == 'restart')) { + $restart = true; + $generation = intval($argv[2]); + if(! $generation) + killme(); + } + + if(($argc > 1) && intval($argv[1])) { + $manual_id = intval($argv[1]); + $force = true; + } + + + $sql_extra = (($manual_id) ? " AND abook_id = " . intval($manual_id) . " " : ""); + + reload_plugins(); + + $d = datetime_convert(); + + // Only poll from those with suitable relationships + + $abandon_sql = (($abandon_days) + ? sprintf(" AND account_lastlog > %s - INTERVAL %s ", db_utcnow(), db_quoteinterval(intval($abandon_days).' DAY')) + : '' + ); + + $randfunc = db_getfunc('RAND'); + + $contacts = q("SELECT * FROM abook LEFT JOIN xchan on abook_xchan = xchan_hash + LEFT JOIN account on abook_account = account_id + where abook_self = 0 + $sql_extra + AND (( account_flags = %d ) OR ( account_flags = %d )) $abandon_sql ORDER BY $randfunc", + intval(ACCOUNT_OK), + intval(ACCOUNT_UNVERIFIED) // FIXME + + ); + + if($contacts) { + + foreach($contacts as $contact) { + + $update = false; + + $t = $contact['abook_updated']; + $c = $contact['abook_connected']; + + if(intval($contact['abook_feed'])) { + $min = service_class_fetch($contact['abook_channel'],'minimum_feedcheck_minutes'); + if(! $min) + $min = intval(get_config('system','minimum_feedcheck_minutes')); + if(! $min) + $min = 60; + $x = datetime_convert('UTC','UTC',"now - $min minutes"); + if($c < $x) { + Master::Summon(array('Onepoll',$contact['abook_id'])); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + continue; + } + + + if($contact['xchan_network'] !== 'zot') + continue; + + if($c == $t) { + if(datetime_convert('UTC','UTC', 'now') > datetime_convert('UTC','UTC', $t . " + 1 day")) + $update = true; + } + else { + + // if we've never connected with them, start the mark for death countdown from now + + if($c == NULL_DATE) { + $r = q("update abook set abook_connected = '%s' where abook_id = %d", + dbesc(datetime_convert()), + intval($contact['abook_id']) + ); + $c = datetime_convert(); + $update = true; + } + + // He's dead, Jim + + if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $c . " + 30 day")) > 0) { + $r = q("update abook set abook_archived = 1 where abook_id = %d", + intval($contact['abook_id']) + ); + $update = false; + continue; + } + + if(intval($contact['abook_archived'])) { + $update = false; + continue; + } + + // might be dead, so maybe don't poll quite so often + + // recently deceased, so keep up the regular schedule for 3 days + + if((strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $c . " + 3 day")) > 0) + && (strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $t . " + 1 day")) > 0)) + $update = true; + + // After that back off and put them on a morphine drip + + if(strcmp(datetime_convert('UTC','UTC', 'now'),datetime_convert('UTC','UTC', $t . " + 2 day")) > 0) { + $update = true; + } + + } + + if(intval($contact['abook_pending']) || intval($contact['abook_archived']) || intval($contact['abook_ignored']) || intval($contact['abook_blocked'])) + continue; + + if((! $update) && (! $force)) + continue; + + Master::Summon(array('Onepoll',$contact['abook_id'])); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + + } + } + + if($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) { + $r = q("SELECT u.ud_addr, u.ud_id, u.ud_last FROM updates AS u INNER JOIN (SELECT ud_addr, max(ud_id) AS ud_id FROM updates WHERE ( ud_flags & %d ) = 0 AND ud_addr != '' AND ( ud_last = '%s' OR ud_last > %s - INTERVAL %s ) GROUP BY ud_addr) AS s ON s.ud_id = u.ud_id ", + intval(UPDATE_FLAGS_UPDATED), + dbesc(NULL_DATE), + db_utcnow(), db_quoteinterval('7 DAY') + ); + if($r) { + foreach($r as $rr) { + + // If they didn't respond when we attempted before, back off to once a day + // After 7 days we won't bother anymore + + if($rr['ud_last'] != NULL_DATE) + if($rr['ud_last'] > datetime_convert('UTC','UTC', 'now - 1 day')) + continue; + Master::Summon(array('Onedirsync',$rr['ud_id'])); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + } + } + + set_config('system','lastpoll',datetime_convert()); + + //All done - clear the lockfile + @unlink($lockfile); + + return; + } +} diff --git a/Zotlabs/Daemon/Queue.php b/Zotlabs/Daemon/Queue.php new file mode 100644 index 000000000..27306589d --- /dev/null +++ b/Zotlabs/Daemon/Queue.php @@ -0,0 +1,90 @@ + 1) + $queue_id = argv(1); + else + $queue_id = 0; + + logger('queue: start'); + + // delete all queue items more than 3 days old + // but first mark these sites dead if we haven't heard from them in a month + + $r = q("select outq_posturl from outq where outq_created < %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('3 DAY') + ); + if($r) { + foreach($r as $rr) { + $site_url = ''; + $h = parse_url($rr['outq_posturl']); + $desturl = $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : ''); + q("update site set site_dead = 1 where site_dead = 0 and site_url = '%s' and site_update < %s - INTERVAL %s", + dbesc($desturl), + db_utcnow(), db_quoteinterval('1 MONTH') + ); + } + } + + $r = q("DELETE FROM outq WHERE outq_created < %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('3 DAY') + ); + + if($queue_id) { + $r = q("SELECT * FROM outq WHERE outq_hash = '%s' LIMIT 1", + dbesc($queue_id) + ); + } + else { + + // For the first 12 hours we'll try to deliver every 15 minutes + // After that, we'll only attempt delivery once per hour. + // This currently only handles the default queue drivers ('zot' or '') which we will group by posturl + // so that we don't start off a thousand deliveries for a couple of dead hubs. + // The zot driver will deliver everything destined for a single hub once contact is made (*if* contact is made). + // Other drivers will have to do something different here and may need their own query. + + // Note: this requires some tweaking as new posts to long dead hubs once a day will keep them in the + // "every 15 minutes" category. We probably need to prioritise them when inserted into the queue + // or just prior to this query based on recent and long-term delivery history. If we have good reason to believe + // the site is permanently down, there's no reason to attempt delivery at all, or at most not more than once + // or twice a day. + + // FIXME: can we sort postgres on outq_priority and maintain the 'distinct' ? + // The order by max(outq_priority) might be a dodgy query because of the group by. + // The desired result is to return a sequence in the order most likely to be delivered in this run. + // If a hub has already been sitting in the queue for a few days, they should be delivered last; + // hence every failure should drop them further down the priority list. + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $prefix = 'DISTINCT ON (outq_posturl)'; + $suffix = 'ORDER BY outq_posturl'; + } else { + $prefix = ''; + $suffix = 'GROUP BY outq_posturl ORDER BY max(outq_priority)'; + } + $r = q("SELECT $prefix * FROM outq WHERE outq_delivered = 0 and (( outq_created > %s - INTERVAL %s and outq_updated < %s - INTERVAL %s ) OR ( outq_updated < %s - INTERVAL %s )) $suffix", + db_utcnow(), db_quoteinterval('12 HOUR'), + db_utcnow(), db_quoteinterval('15 MINUTE'), + db_utcnow(), db_quoteinterval('1 HOUR') + ); + } + if(! $r) + return; + + foreach($r as $rr) { + queue_deliver($rr); + } + } +} diff --git a/Zotlabs/Daemon/README.md b/Zotlabs/Daemon/README.md new file mode 100644 index 000000000..cb5b00a56 --- /dev/null +++ b/Zotlabs/Daemon/README.md @@ -0,0 +1,43 @@ +Daemon (background) Processes +============================= + + +This directory provides background tasks which are executed by a +command-line process and detached from normal web processing. + +Background tasks are invoked by calling + + + Zotlabs\Daemon\Master::Summon([ $cmd, $arg1, $argn... ]); + +The Master class loads the desired command file and passes the arguments. + + +To create a background task 'Foo' use the following template. + + 'rating', + 'encoding' => 'zot', + 'target' => $r[0]['xlink_link'], + 'rating' => intval($r[0]['xlink_rating']), + 'rating_text' => $r[0]['xlink_rating_text'], + 'signature' => $r[0]['xlink_sig'], + 'edited' => $r[0]['xlink_updated'] + ); + } + + $channel = channelx_by_hash($r[0]['xlink_xchan']); + if(! $channel) { + logger('no channel'); + return; + } + + + $primary = get_directory_primary(); + + if(! $primary) + return; + + + $interval = ((get_config('system','delivery_interval') !== false) + ? intval(get_config('system','delivery_interval')) : 2 ); + + $deliveries_per_process = intval(get_config('system','delivery_batch_count')); + + if($deliveries_per_process <= 0) + $deliveries_per_process = 1; + + $deliver = array(); + + $x = z_fetch_url($primary . '/regdir'); + if($x['success']) { + $j = json_decode($x['body'],true); + if($j && $j['success'] && is_array($j['directories'])) { + + foreach($j['directories'] as $h) { + if($h == z_root()) + continue; + + $hash = random_string(); + $n = zot_build_packet($channel,'notify',null,null,$hash); + + queue_insert(array( + 'hash' => $hash, + 'account_id' => $channel['channel_account_id'], + 'channel_id' => $channel['channel_id'], + 'posturl' => $h . '/post', + 'notify' => $n, + 'msg' => json_encode($encoded_item) + )); + + $deliver[] = $hash; + + if(count($deliver) >= $deliveries_per_process) { + Master::Summon(array('Deliver',$deliver)); + $deliver = array(); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); + } + } + + // catch any stragglers + + if(count($deliver)) { + Master::Summon(array('Deliver',$deliver)); + } + } + } + + logger('ratenotif: complete.'); + return; + + } +} diff --git a/Zotlabs/Extend/Hook.php b/Zotlabs/Extend/Hook.php new file mode 100644 index 000000000..fc1e95367 --- /dev/null +++ b/Zotlabs/Extend/Hook.php @@ -0,0 +1,107 @@ + $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('
',"\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/Cache.php b/Zotlabs/Lib/Cache.php new file mode 100644 index 000000000..35c8f56ad --- /dev/null +++ b/Zotlabs/Lib/Cache.php @@ -0,0 +1,46 @@ + 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 @@ +'); + + 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, '' . $sitename . ''); + $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('',$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, '' . $sitename . ''); + } + + 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, '' . $sitename . ''); + $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, '' . $sitename . ''); + $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, '' . $sitename . ''); + $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, '' . $sitename . ''); + $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, '' . $sitename . ''); + $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, '' . $sitename . ''); + $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("","
\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'] = '' . $itemlink . ''; + $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 @@ + $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..319b8f203 --- /dev/null +++ b/Zotlabs/Lib/PConfig.php @@ -0,0 +1,192 @@ +global_perm = $global_perm; + $this->channel_perm = $channel_perm; + + $this->fallback_description = ($description == '') ? t('Visible to your default audience') : $description; + } + + /** + * If the interpretation of an empty ACL can't be summarised with a global default permission + * or a specific permission setting then use this method and describe what it means instead. + * Remember to localize the description first. + * + * @param string $description - the localized caption for the no-ACL option in the ACL dialog. + * @return a new instance of PermissionDescription + */ + public static function fromDescription($description) { + return new PermissionDescription('', 0x80000, $description); + } + + + /** + * Use this method only if the interpretation of an empty ACL doesn't fall back to a global + * default permission. You should pass one of the constants from boot.php - PERMS_PUBLIC, + * PERMS_NETWORK etc. + * + * @param integer $perm - a single enumerated constant permission - PERMS_PUBLIC, PERMS_NETWORK etc. + * @return a new instance of PermissionDescription + */ + public static function fromStandalonePermission($perm) { + + $result = new PermissionDescription('', $perm); + + $checkPerm = $this->get_permission_description(); + if ($checkPerm == $this->fallback_description) { + $result = null; + logger('null PermissionDescription from unknown standalone permission: ' . $perm ,LOGGER_DEBUG, LOG_ERROR); + } + + return $result; + } + + /** + * This is the preferred way to create a PermissionDescription, as it provides the most details. + * Use this method if you know an empty ACL will result in one of the global default permissions + * being used, such as channel_r_stream (for which you would pass 'view_stream'). + * + * @param string $permname - a key for the global perms array from get_perms() in permissions.php, + * e.g. 'view_stream', 'view_profile', etc. + * @return a new instance of PermissionDescription + */ + public static function fromGlobalPermission($permname) { + + $result = null; + + $global_perms = \Zotlabs\Access\Permissions::Perms(); + + if (array_key_exists($permname, $global_perms)) { + + $channelPerm = \Zotlabs\Access\PermissionLimits::Get(\App::$channel['channel_id'],$permname); + + $result = new PermissionDescription('', $channelPerm); + } else { + // The acl dialog can handle null arguments, but it shouldn't happen + logger('null PermissionDescription from unknown global permission: ' . $permname ,LOGGER_DEBUG, LOG_ERROR); + } + return $result; + } + + + /** + * Gets a localized description of the permission, or a generic message if the permission + * is unknown. + * + * @return string description + */ + public function get_permission_description() { + + switch($this->channel_perm) { + + case 0: return t('Only me'); + case PERMS_PUBLIC: return t('Public'); + case PERMS_NETWORK: return t('Anybody in the $Projectname network'); + case PERMS_SITE: return sprintf(t('Any account on %s'), \App::get_hostname()); + case PERMS_CONTACTS: return t('Any of my connections'); + case PERMS_SPECIFIC: return t('Only connections I specifically allow'); + case PERMS_AUTHED: return t('Anybody authenticated (could include visitors from other networks)'); + case PERMS_PENDING: return t('Any connections including those who haven\'t yet been approved'); + default: return $this->fallback_description; + } + } + + /** + * Returns an icon css class name if an appropriate one is available, e.g. "fa-globe" for Public, + * otherwise returns empty string. + * + * @return string icon css class name (often FontAwesome) + */ + public function get_permission_icon() { + + switch($this->channel_perm) { + + case 0:/* only me */ return 'fa-eye-slash'; + case PERMS_PUBLIC: return 'fa-globe'; + case PERMS_NETWORK: return 'fa-share-alt-square'; // fa-share-alt-square is very similiar to the hubzilla logo, but we should create our own logo class to use + case PERMS_SITE: return 'fa-sitemap'; + case PERMS_CONTACTS: return 'fa-group'; + case PERMS_SPECIFIC: return 'fa-list'; + case PERMS_AUTHED: return ''; + case PERMS_PENDING: return ''; + default: return ''; + } + } + + + /** + * Returns a localized description of where the permission came from, if this is known. + * If it's not know, or if the permission is standalone and didn't come from a default + * permission setting, then empty string is returned. + * + * @return string description or empty string + */ + public function get_permission_origin_description() { + + switch($this->global_perm) { + + case PERMS_R_STREAM: return t('This is your default setting for the audience of your normal stream, and posts.'); + case PERMS_R_PROFILE: return t('This is your default setting for who can view your default channel profile'); + case PERMS_R_ABOOK: return t('This is your default setting for who can view your connections'); + case PERMS_R_STORAGE: return t('This is your default setting for who can view your file storage and photos'); + case PERMS_R_PAGES: return t('This is your default setting for the audience of your webpages'); + default: return ''; + } + } + +} 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 @@ +request_data = $s; + $this->filepos = 0; + } + + public function curl_read($ch,$fh,$size) { + + if($this->filepos < 0) { + unset($fh); + return ''; + } + + $s = substr($this->request_data,$this->filepos,$size); + + if(strlen($s) < $size) + $this->filepos = (-1); + else + $this->filepos = $this->filepos + $size; + + return $s; + } + + + public function __construct($opts = array()) { + $this->set($opts); + } + + private function set($opts = array()) { + if($opts) { + foreach($opts as $k => $v) { + switch($k) { + case 'http_auth': + $this->auth = $v; + break; + case 'magicauth': + // currently experimental + $this->magicauth = $v; + \Zotlabs\Daemon\Master::Summon([ 'CurlAuth', $v ]); + break; + case 'custom': + $this->request_method = $v; + break; + case 'url': + $this->url = $v; + break; + case 'data': + $this->set_data($v); + if($v) { + $this->upload = true; + } + else { + $this->upload = false; + } + break; + case 'headers': + $this->headers = $v; + break; + default: + $this->curlopts[$k] = $v; + break; + } + } + } + } + + function exec() { + $opts = $this->curlopts; + $url = $this->url; + if($this->auth) + $opts['http_auth'] = $this->auth; + if($this->magicauth) { + $opts['cookiejar'] = 'store/[data]/cookie_' . $this->magicauth; + $opts['cookiefile'] = 'store/[data]/cookie_' . $this->magicauth; + $opts['cookie'] = 'PHPSESSID=' . trim(file_get_contents('store/[data]/cookien_' . $this->magicauth)); + $c = channelx_by_n($this->magicauth); + if($c) + $url = zid($this->url,$c['channel_address'] . '@' . \App::get_hostname()); + } + if($this->custom) + $opts['custom'] = $this->custom; + if($this->headers) + $opts['headers'] = $this->headers; + if($this->upload) { + $opts['upload'] = true; + $opts['infile'] = $this->filehandle; + $opts['infilesize'] = strlen($this->request_data); + $opts['readfunc'] = [ $this, 'curl_read' ] ; + } + + $recurse = 0; + return z_fetch_url($this->url,true,$recurse,(($opts) ? $opts : null)); + + } + + +} 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 @@ +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, '' . t('View all') . ''); + } 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, '' . t('View all') . ''); + } 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['ttype'] == 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'] = sprintf( t('%s 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 @@ +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..7f3d0f2cd --- /dev/null +++ b/Zotlabs/Lib/XConfig.php @@ -0,0 +1,160 @@ + 1) + $which = argv(1); + else { + notice( t('Requested profile is not available.') . EOL ); + return; + } + + $profile = 0; + $profile = argv(1); + profile_load($which,$profile); + + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + + $observer = \App::get_observer(); + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + $perms = get_all_perms($owner,$ob_hash); + if(! $perms['view_profile']) { + notice( t('Permission denied.') . EOL); + return; + } + + $newmembertext = t('Some blurb about what to do when you\'re new here'); + + + // By default, all badges are false + $contactbadge = false; + $profilebadge = false; + $keywordsbadge = false; + + // Check number of contacts. Award a badge if over 10 + // We'll figure these out on each page load instead of + // writing them to the DB because that will mean one needs + // to retain their achievements - eg, you can't add + // a bunch of channels just to get your badge, and then + // delete them all again. If these become popular or + // used in profiles or something, we may need to reconsider + // and add a table for this - because this won't scale. + + $r = q("select * from abook where abook_channel = %d", + intval($owner) + ); + + if (count($r)) + $contacts = count($r); + // We're checking for 11 to adjust for the abook record for self + if ($contacts >= 11) + $contactbadge = true; + + // Check if an about field in the profile has been created. + + $r = q("select * from profile where uid = %d and about <> ''", + intval($owner) + ); + + if ($r) + $profilebadge = 1; + + // Check if keywords have been set + + $r = q("select * from profile where uid = %d and keywords <> ''", + intval($owner) + ); + + if($r) + $keywordsbadge = 1; + + return replace_macros(get_markup_template("achievements.tpl"), array( + '$newmembertext' => $newmembertext, + '$profilebadge' => $profilebadge, + '$contactbadge' => $contactbadge, + '$keywordsbadge' => $keywordsbadge, + '$channelsbadge' => $channelsbadge + )); + + } + +} diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php new file mode 100644 index 000000000..03dc6c5d3 --- /dev/null +++ b/Zotlabs/Module/Acl.php @@ -0,0 +1,401 @@ + standard ACL request + // 'g' => Groups only ACL request + // 'c' => Connections only ACL request or editor (textarea) mention request + // $_REQUEST['search'] contains ACL search text. + + + // $type = + // 'm' => autocomplete private mail recipient (checks post_mail permission) + // 'a' => autocomplete connections (mod_connections, mod_poke, mod_sources, mod_photos) + // 'x' => nav search bar autocomplete (match any xchan) + // $_REQUEST['query'] contains autocomplete search text. + + // List of channels whose connections to also suggest, + // e.g. currently viewed channel or channels mentioned in a post + + $extra_channels = (x($_REQUEST,'extra_channels') ? $_REQUEST['extra_channels'] : array()); + + // The different autocomplete libraries use different names for the search text + // parameter. Internaly we'll use $search to represent the search text no matter + // what request variable it was attached to. + + if(array_key_exists('query',$_REQUEST)) { + $search = $_REQUEST['query']; + } + + if( (! local_channel()) && (! ($type == 'x' || $type == 'c'))) + killme(); + + $permitted = []; + + if(in_array($type, [ 'm', 'a', 'c' ])) { + + // These queries require permission checking. We'll create a simple array of xchan_hash for those with + // the requisite permissions which we can check against. + + $x = q("select xchan from abconfig where chan = %d and cat = 'their_perms' and k = '%s' and v = 1", + intval(local_channel()), + dbesc(($type === 'm') ? 'post_mail' : 'tag_deliver') + ); + + $permitted = ids_to_array($x,'xchan'); + } + + + if($search) { + $sql_extra = " AND `name` LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; + $sql_extra2 = "AND ( xchan_name LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " OR xchan_addr LIKE " . protect_sprintf( "'%" . dbesc($search) . ((strpos($search,'@') === false) ? "%@%'" : "%'")) . ") "; + + // This horrible mess is needed because position also returns 0 if nothing is found. + // Would be MUCH easier if it instead returned a very large value + // Otherwise we could just + // order by LEAST(POSITION($search IN xchan_name),POSITION($search IN xchan_addr)). + + $order_extra2 = "CASE WHEN xchan_name LIKE " + . protect_sprintf( "'%" . dbesc($search) . "%'" ) + . " then POSITION('" . dbesc($search) + . "' IN xchan_name) else position('" . dbesc($search) . "' IN xchan_addr) end, "; + + $col = ((strpos($search,'@') !== false) ? 'xchan_addr' : 'xchan_name' ); + $sql_extra3 = "AND $col like " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; + + } + else { + $sql_extra = $sql_extra2 = $sql_extra3 = ""; + } + + + $groups = array(); + $contacts = array(); + + if($type == '' || $type == 'g') { + + $r = q("SELECT groups.id, groups.hash, groups.gname + FROM groups,group_member + WHERE groups.deleted = 0 AND groups.uid = %d + AND group_member.gid=groups.id + $sql_extra + GROUP BY groups.id + ORDER BY groups.gname + LIMIT %d OFFSET %d", + intval(local_channel()), + intval($count), + intval($start) + ); + + if($r) { + foreach($r as $g){ + // logger('acl: group: ' . $g['gname'] . ' members: ' . group_get_members_xchan($g['id'])); + $groups[] = array( + "type" => "g", + "photo" => "images/twopeople.png", + "name" => $g['gname'], + "id" => $g['id'], + "xid" => $g['hash'], + "uids" => group_get_members_xchan($g['id']), + "link" => '' + ); + } + } + } + + if($type == '' || $type == 'c') { + $extra_channels_sql = ''; + // Only include channels who allow the observer to view their permissions + foreach($extra_channels as $channel) { + if(perm_is_allowed(intval($channel), get_observer_hash(),'view_contacts')) + $extra_channels_sql .= "," . intval($channel); + } + + $extra_channels_sql = substr($extra_channels_sql,1); // Remove initial comma + + // Getting info from the abook is better for local users because it contains info about permissions + if(local_channel()) { + if($extra_channels_sql != '') + $extra_channels_sql = " OR (abook_channel IN ($extra_channels_sql)) and abook_hidden = 0 "; + + $r2 = null; + + $r1 = q("select * from atoken where atoken_uid = %d", + intval(local_channel()) + ); + if($r1) { + require_once('include/security.php'); + $r2 = array(); + foreach($r1 as $rr) { + $x = atoken_xchan($rr); + $r2[] = [ + 'id' => 'a' . $rr['atoken_id'] , + 'hash' => $x['xchan_hash'], + 'name' => $x['xchan_name'], + 'micro' => $x['xchan_photo_m'], + 'url' => z_root(), + 'nick' => $x['xchan_addr'], + 'abook_their_perms' => 0, + 'abook_flags' => 0, + 'abook_self' => 0 + ]; + } + } + + + $r = q("SELECT abook_id as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, abook_their_perms, xchan_pubforum, abook_flags, abook_self + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE (abook_channel = %d $extra_channels_sql) AND abook_blocked = 0 and abook_pending = 0 and xchan_deleted = 0 $sql_extra2 order by $order_extra2 xchan_name asc" , + intval(local_channel()) + ); + if($r2) + $r = array_merge($r2,$r); + + } + else { // Visitors + $r = q("SELECT xchan_hash as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, 0 as abook_their_perms, 0 as abook_flags, 0 as abook_self + FROM xchan left join xlink on xlink_link = xchan_hash + WHERE xlink_xchan = '%s' AND xchan_deleted = 0 $sql_extra2 order by $order_extra2 xchan_name asc" , + dbesc(get_observer_hash()) + ); + + // Find contacts of extra channels + // This is probably more complicated than it needs to be + if($extra_channels_sql) { + // Build a list of hashes that we got previously so we don't get them again + $known_hashes = array("'".get_observer_hash()."'"); + if($r) + foreach($r as $rr) + $known_hashes[] = "'".$rr['hash']."'"; + $known_hashes_sql = 'AND xchan_hash not in ('.join(',',$known_hashes).')'; + + $r2 = q("SELECT abook_id as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, abook_their_perms, abook_flags, abook_self + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel IN ($extra_channels_sql) $known_hashes_sql AND abook_blocked = 0 and abook_pending = 0 and abook_hidden = 0 and xchan_deleted = 0 $sql_extra2 order by $order_extra2 xchan_name asc"); + if($r2) + $r = array_merge($r,$r2); + + // Sort accoring to match position, then alphabetically. This could be avoided if the above two SQL queries could be combined into one, and the sorting could be done on the SQl server (like in the case of a local user) + $matchpos = function($x) use($search) { + $namepos = strpos($x['name'],$search); + $nickpos = strpos($x['nick'],$search); + // Use a large position if not found + return min($namepos === false ? 9999 : $namepos, $nickpos === false ? 9999 : $nickpos); + }; + // This could be made simpler if PHP supported stable sorting + usort($r,function($a,$b) use($matchpos) { + $pos1 = $matchpos($a); + $pos2 = $matchpos($b); + if($pos1 == $pos2) { // Order alphabetically if match position is the same + if($a['name'] == $b['name']) + return 0; + else + return ($a['name'] < $b['name']) ? -1 : 1; + } + return ($pos1 < $pos2) ? -1 : 1; + }); + } + } + if(intval(get_config('system','taganyone')) || intval(get_pconfig(local_channel(),'system','taganyone'))) { + if((count($r) < 100) && $type == 'c') { + $r2 = q("SELECT substr(xchan_hash,1,18) as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, 0 as abook_their_perms, 0 as abook_flags, 0 as abook_self + FROM xchan + WHERE xchan_deleted = 0 $sql_extra2 order by $order_extra2 xchan_name asc" + ); + if($r2) + $r = array_merge($r,$r2); + } + } + } + elseif($type == 'm') { + + $r = array(); + $z = q("SELECT xchan_hash as hash, xchan_name as name, xchan_addr as nick, xchan_photo_s as micro, xchan_url as url + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d + and xchan_deleted = 0 + $sql_extra3 + ORDER BY xchan_name ASC ", + intval(local_channel()) + ); + if($z) { + foreach($z as $zz) { + if(in_array($zz['id'],$permitted)) { + $r[] = $zz; + } + } + } + + } + elseif($type == 'a') { + + $r = q("SELECT abook_id as id, xchan_name as name, xchan_hash as hash, xchan_addr as nick, xchan_photo_s as micro, xchan_network as network, xchan_url as url, xchan_addr as attag , abook_their_perms FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d + and xchan_deleted = 0 + $sql_extra3 + ORDER BY xchan_name ASC ", + intval(local_channel()) + ); + + } + elseif($type == 'x') { + $r = $this->navbar_complete($a); + $contacts = array(); + if($r) { + foreach($r as $g) { + $contacts[] = array( + "photo" => $g['photo'], + "name" => $g['name'], + "nick" => $g['address'], + ); + } + } + + $o = array( + 'start' => $start, + 'count' => $count, + 'items' => $contacts, + ); + echo json_encode($o); + killme(); + } + else + $r = array(); + + if($r) { + foreach($r as $g){ + + // remove RSS feeds from ACLs - they are inaccessible + if(strpos($g['hash'],'/') && $type != 'a') + continue; + + if(in_array($g['hash'],$permitted) && $type == 'c' && (! $noforums)) { + $contacts[] = array( + "type" => "c", + "photo" => "images/twopeople.png", + "name" => $g['name'] . '+', + "id" => $g['id'] . '+', + "xid" => $g['hash'], + "link" => $g['nick'], + "nick" => substr($g['nick'],0,strpos($g['nick'],'@')), + "self" => (intval($g['abook_self']) ? 'abook-self' : ''), + "taggable" => 'taggable', + "label" => t('network') + ); + } + $contacts[] = array( + "type" => "c", + "photo" => $g['micro'], + "name" => $g['name'], + "id" => $g['id'], + "xid" => $g['hash'], + "link" => $g['nick'], + "nick" => (($g['nick']) ? substr($g['nick'],0,strpos($g['nick'],'@')) : t('RSS')), + "self" => (intval($g['abook_self']) ? 'abook-self' : ''), + "taggable" => '', + "label" => '', + ); + } + } + + $items = array_merge($groups, $contacts); + + $o = array( + 'start' => $start, + 'count' => $count, + 'items' => $items, + ); + + + + echo json_encode($o); + + killme(); + } + + + function navbar_complete(&$a) { + + // logger('navbar_complete'); + + if(observer_prohibited()) { + return; + } + + $dirmode = intval(get_config('system','directory_mode')); + $search = ((x($_REQUEST,'search')) ? htmlentities($_REQUEST['search'],ENT_COMPAT,'UTF-8',false) : ''); + if(! $search || mb_strlen($search) < 2) + return array(); + + $star = false; + $address = false; + + if(substr($search,0,1) === '@') + $search = substr($search,1); + + if(substr($search,0,1) === '*') { + $star = true; + $search = substr($search,1); + } + + if(strpos($search,'@') !== false) { + $address = true; + } + + if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) { + $url = z_root() . '/dirsearch'; + } + + if(! $url) { + require_once("include/dir_fns.php"); + $directory = find_upstream_directory($dirmode); + $url = $directory['url'] . '/dirsearch'; + } + + $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 100); + if($url) { + $query = $url . '?f=' ; + $query .= '&name=' . urlencode($search) . "&limit=$count" . (($address) ? '&address=' . urlencode($search) : ''); + + $x = z_fetch_url($query); + if($x['success']) { + $t = 0; + $j = json_decode($x['body'],true); + if($j && $j['results']) { + return $j['results']; + } + } + } + return array(); + } + +} diff --git a/Zotlabs/Module/Admin.php b/Zotlabs/Module/Admin.php new file mode 100644 index 000000000..085d13fd7 --- /dev/null +++ b/Zotlabs/Module/Admin.php @@ -0,0 +1,2124 @@ + 1) { + switch (argv(1)) { + case 'site': + $this->admin_page_site_post($a); + break; + case 'accounts': + $this->admin_page_accounts_post($a); + break; + case 'channels': + $this->admin_page_channels_post($a); + break; + case 'plugins': + if (argc() > 2 && argv(2) === 'addrepo') { + $this->admin_page_plugins_post('addrepo'); + break; + } + if (argc() > 2 && argv(2) === 'installrepo') { + $this->admin_page_plugins_post('installrepo'); + break; + } + if (argc() > 2 && argv(2) === 'removerepo') { + $this->admin_page_plugins_post('removerepo'); + break; + } + if (argc() > 2 && argv(2) === 'updaterepo') { + $this->admin_page_plugins_post('updaterepo'); + break; + } + if (argc() > 2 && + is_file("addon/" . argv(2) . "/" . argv(2) . ".php")){ + @include_once("addon/" . argv(2) . "/" . argv(2) . ".php"); + if(function_exists(argv(2).'_plugin_admin_post')) { + $func = argv(2) . '_plugin_admin_post'; + $func($a); + } + } + goaway(z_root() . '/admin/plugins/' . argv(2) ); + break; + case 'themes': + $theme = argv(2); + if (is_file("view/theme/$theme/php/config.php")){ + require_once("view/theme/$theme/php/config.php"); + // fixme add parent theme if derived + if (function_exists("theme_admin_post")){ + theme_admin_post($a); + } + } + info(t('Theme settings updated.')); + if(is_ajax()) return; + + goaway(z_root() . '/admin/themes/' . $theme ); + break; + case 'logs': + $this->admin_page_logs_post($a); + break; + case 'hubloc': + $this->admin_page_hubloc_post($a); + break; + case 'security': + $this->admin_page_security_post($a); + break; + case 'features': + $this->admin_page_features_post($a); + break; + case 'dbsync': + $this->admin_page_dbsync_post($a); + break; + case 'profs': + $this->admin_page_profs_post($a); + break; + } + } + + goaway(z_root() . '/admin' ); + } + + /** + * @param App &$a + * @return string + */ + function get() { + + logger('admin_content', LOGGER_DEBUG); + + if(! is_site_admin()) { + return login(false); + } + + + /* + * Page content + */ + $o = ''; + + // urls + if (argc() > 1){ + switch (argv(1)) { + case 'site': + $o = $this->admin_page_site($a); + break; + case 'accounts': + $o = $this->admin_page_accounts($a); + break; + case 'channels': + $o = $this->admin_page_channels($a); + break; + case 'plugins': + $o = $this->admin_page_plugins($a); + break; + case 'themes': + $o = $this->admin_page_themes($a); + break; + // case 'hubloc': + // $o = $this->admin_page_hubloc($a); + // break; + case 'security': + $o = $this->admin_page_security($a); + break; + case 'features': + $o = $this->admin_page_features($a); + break; + case 'logs': + $o = $this->admin_page_logs($a); + break; + case 'dbsync': + $o = $this->admin_page_dbsync($a); + break; + case 'profs': + $o = $this->admin_page_profs($a); + break; + case 'queue': + $o = $this->admin_page_queue($a); + break; + default: + notice( t('Item not found.') ); + } + } else { + $o = $this->admin_page_summary($a); + } + + if(is_ajax()) { + echo $o; + killme(); + return ''; + } else { + return $o; + } + } + + + /** + * @brief Returns content for Admin Summary Page. + * + * @param App &$a + * @return string HTML from parsed admin_summary.tpl + */ + function admin_page_summary(&$a) { + + // list total user accounts, expirations etc. + $accounts = array(); + $r = q("SELECT COUNT(*) AS total, COUNT(CASE WHEN account_expires > %s THEN 1 ELSE NULL END) AS expiring, COUNT(CASE WHEN account_expires < %s AND account_expires != '%s' THEN 1 ELSE NULL END) AS expired, COUNT(CASE WHEN (account_flags & %d)>0 THEN 1 ELSE NULL END) AS blocked FROM account", + db_utcnow(), + db_utcnow(), + dbesc(NULL_DATE), + intval(ACCOUNT_BLOCKED) + ); + if ($r) { + $accounts['total'] = array('label' => t('# Accounts'), 'val' => $r[0]['total']); + $accounts['blocked'] = array('label' => t('# blocked accounts'), 'val' => $r[0]['blocked']); + $accounts['expired'] = array('label' => t('# expired accounts'), 'val' => $r[0]['expired']); + $accounts['expiring'] = array('label' => t('# expiring accounts'), 'val' => $r[0]['expiring']); + } + + // pending registrations + $r = q("SELECT COUNT(id) AS `count` FROM `register` WHERE `uid` != '0'"); + $pending = $r[0]['count']; + + // available channels, primary and clones + $channels = array(); + $r = q("SELECT COUNT(*) AS total, COUNT(CASE WHEN channel_primary = 1 THEN 1 ELSE NULL END) AS main, COUNT(CASE WHEN channel_primary = 0 THEN 1 ELSE NULL END) AS clones FROM channel WHERE channel_removed = 0"); + if ($r) { + $channels['total'] = array('label' => t('# Channels'), 'val' => $r[0]['total']); + $channels['main'] = array('label' => t('# primary'), 'val' => $r[0]['main']); + $channels['clones'] = array('label' => t('# clones'), 'val' => $r[0]['clones']); + } + + // We can do better, but this is a quick queue status + $r = q("SELECT COUNT(outq_delivered) AS total FROM outq WHERE outq_delivered = 0"); + $queue = (($r) ? $r[0]['total'] : 0); + $queues = array( 'label' => t('Message queues'), 'queue' => $queue ); + + // If no plugins active return 0, otherwise list of plugin names + $plugins = (count(\App::$plugins) == 0) ? count(\App::$plugins) : \App::$plugins; + + // Could be extended to provide also other alerts to the admin + $alertmsg = ''; + // annoy admin about upcoming unsupported PHP version + if (version_compare(PHP_VERSION, '5.4', '<')) { + $alertmsg = 'Your PHP version ' . PHP_VERSION . ' will not be supported with the next major release of $Projectname. You are strongly urged to upgrade to a current version.' + . '
PHP 5.3 has reached its End of Life (EOL) in August 2014.' + . ' A list about current PHP versions can be found here.'; + } + + $vmaster = get_repository_version('master'); + $vdev = get_repository_version('dev'); + + $upgrade = ((version_compare(STD_VERSION,$vmaster) < 0) ? t('Your software should be updated') : ''); + + + $t = get_markup_template('admin_summary.tpl'); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Summary'), + '$adminalertmsg' => $alertmsg, + '$queues' => $queues, + '$accounts' => array( t('Registered accounts'), $accounts), + '$pending' => array( t('Pending registrations'), $pending), + '$channels' => array( t('Registered channels'), $channels), + '$plugins' => array( t('Active plugins'), $plugins ), + '$version' => array( t('Version'), STD_VERSION), + '$vmaster' => array( t('Repository version (master)'), $vmaster), + '$vdev' => array( t('Repository version (dev)'), $vdev), + '$upgrade' => $upgrade, + '$build' => get_config('system', 'db_version') + )); + } + + + /** + * @brief POST handler for Admin Site Page. + * + * @param App &$a + */ + function admin_page_site_post(&$a){ + if (!x($_POST, 'page_site')){ + return; + } + + check_form_security_token_redirectOnErr('/admin/site', 'admin_site'); + + $sitename = ((x($_POST,'sitename')) ? notags(trim($_POST['sitename'])) : ''); + $banner = ((x($_POST,'banner')) ? trim($_POST['banner']) : false); + $admininfo = ((x($_POST,'admininfo')) ? trim($_POST['admininfo']) : false); + $language = ((x($_POST,'language')) ? notags(trim($_POST['language'])) : ''); + $theme = ((x($_POST,'theme')) ? notags(trim($_POST['theme'])) : ''); + $theme_mobile = ((x($_POST,'theme_mobile')) ? notags(trim($_POST['theme_mobile'])) : ''); + // $site_channel = ((x($_POST,'site_channel')) ? notags(trim($_POST['site_channel'])) : ''); + $maximagesize = ((x($_POST,'maximagesize')) ? intval(trim($_POST['maximagesize'])) : 0); + + $register_policy = ((x($_POST,'register_policy')) ? intval(trim($_POST['register_policy'])) : 0); + + $access_policy = ((x($_POST,'access_policy')) ? intval(trim($_POST['access_policy'])) : 0); + $invite_only = ((x($_POST,'invite_only')) ? True : False); + $abandon_days = ((x($_POST,'abandon_days')) ? intval(trim($_POST['abandon_days'])) : 0); + + $register_text = ((x($_POST,'register_text')) ? notags(trim($_POST['register_text'])) : ''); + $frontpage = ((x($_POST,'frontpage')) ? notags(trim($_POST['frontpage'])) : ''); + $mirror_frontpage = ((x($_POST,'mirror_frontpage')) ? intval(trim($_POST['mirror_frontpage'])) : 0); + $directory_server = ((x($_POST,'directory_server')) ? trim($_POST['directory_server']) : ''); + $allowed_sites = ((x($_POST,'allowed_sites')) ? notags(trim($_POST['allowed_sites'])) : ''); + $allowed_email = ((x($_POST,'allowed_email')) ? notags(trim($_POST['allowed_email'])) : ''); + $not_allowed_email = ((x($_POST,'not_allowed_email')) ? notags(trim($_POST['not_allowed_email'])) : ''); + $force_publish = ((x($_POST,'publish_all')) ? True : False); + $disable_discover_tab = ((x($_POST,'disable_discover_tab')) ? False : True); + $login_on_homepage = ((x($_POST,'login_on_homepage')) ? True : False); + $enable_context_help = ((x($_POST,'enable_context_help')) ? True : False); + $global_directory = ((x($_POST,'directory_submit_url')) ? notags(trim($_POST['directory_submit_url'])) : ''); + $no_community_page = !((x($_POST,'no_community_page')) ? True : False); + $default_expire_days = ((array_key_exists('default_expire_days',$_POST)) ? intval($_POST['default_expire_days']) : 0); + + $verifyssl = ((x($_POST,'verifyssl')) ? True : False); + $proxyuser = ((x($_POST,'proxyuser')) ? notags(trim($_POST['proxyuser'])) : ''); + $proxy = ((x($_POST,'proxy')) ? notags(trim($_POST['proxy'])) : ''); + $timeout = ((x($_POST,'timeout')) ? intval(trim($_POST['timeout'])) : 60); + $delivery_interval = ((x($_POST,'delivery_interval'))? intval(trim($_POST['delivery_interval'])) : 0); + $delivery_batch_count = ((x($_POST,'delivery_batch_count') && $_POST['delivery_batch_count'] > 0)? intval(trim($_POST['delivery_batch_count'])) : 1); + $poll_interval = ((x($_POST,'poll_interval')) ? intval(trim($_POST['poll_interval'])) : 0); + $maxloadavg = ((x($_POST,'maxloadavg')) ? intval(trim($_POST['maxloadavg'])) : 50); + $feed_contacts = ((x($_POST,'feed_contacts')) ? intval($_POST['feed_contacts']) : 0); + $verify_email = ((x($_POST,'verify_email')) ? 1 : 0); + + set_config('system', 'feed_contacts', $feed_contacts); + set_config('system', 'delivery_interval', $delivery_interval); + set_config('system', 'delivery_batch_count', $delivery_batch_count); + set_config('system', 'poll_interval', $poll_interval); + set_config('system', 'maxloadavg', $maxloadavg); + set_config('system', 'frontpage', $frontpage); + set_config('system', 'mirror_frontpage', $mirror_frontpage); + set_config('system', 'sitename', $sitename); + set_config('system', 'login_on_homepage', $login_on_homepage); + set_config('system', 'enable_context_help', $enable_context_help); + set_config('system', 'verify_email', $verify_email); + set_config('system', 'default_expire_days', $default_expire_days); + + if($directory_server) + set_config('system','directory_server',$directory_server); + + if ($banner == '') { + del_config('system', 'banner'); + } else { + set_config('system', 'banner', $banner); + } + + if ($admininfo == ''){ + del_config('system', 'admininfo'); + } else { + require_once('include/text.php'); + linkify_tags($a, $admininfo, local_channel()); + set_config('system', 'admininfo', $admininfo); + } + set_config('system', 'language', $language); + set_config('system', 'theme', $theme); + if ( $theme_mobile === '---' ) { + del_config('system', 'mobile_theme'); + } else { + set_config('system', 'mobile_theme', $theme_mobile); + } + // set_config('system','site_channel', $site_channel); + set_config('system','maximagesize', $maximagesize); + + set_config('system','register_policy', $register_policy); + set_config('system','invitation_only', $invite_only); + set_config('system','access_policy', $access_policy); + set_config('system','account_abandon_days', $abandon_days); + set_config('system','register_text', $register_text); + set_config('system','allowed_sites', $allowed_sites); + set_config('system','allowed_email', $allowed_email); + set_config('system','not_allowed_email', $not_allowed_email); + set_config('system','publish_all', $force_publish); + set_config('system','disable_discover_tab', $disable_discover_tab); + if ($global_directory == '') { + del_config('system', 'directory_submit_url'); + } else { + set_config('system', 'directory_submit_url', $global_directory); + } + + set_config('system','no_community_page', $no_community_page); + set_config('system','no_utf', $no_utf); + set_config('system','verifyssl', $verifyssl); + set_config('system','proxyuser', $proxyuser); + set_config('system','proxy', $proxy); + set_config('system','curl_timeout', $timeout); + + info( t('Site settings updated.') . EOL); + goaway(z_root() . '/admin/site' ); + } + + /** + * @brief Admin page site. + * + * @param App $a + * @return string + */ + function admin_page_site(&$a) { + + /* Installed langs */ + $lang_choices = array(); + $langs = glob('view/*/hstrings.php'); + + if(is_array($langs) && count($langs)) { + if(! in_array('view/en/hstrings.php',$langs)) + $langs[] = 'view/en/'; + asort($langs); + foreach($langs as $l) { + $t = explode("/",$l); + $lang_choices[$t[1]] = $t[1]; + } + } + + /* Installed themes */ + $theme_choices_mobile["---"] = t("Default"); + $theme_choices = array(); + $files = glob('view/theme/*'); + if($files) { + foreach($files as $file) { + $vars = ''; + $f = basename($file); + if (file_exists($file . '/library')) + continue; + if (file_exists($file . '/mobile')) + $vars = t('mobile'); + if (file_exists($file . '/experimental')) + $vars .= t('experimental'); + if (file_exists($file . '/unsupported')) + $vars .= t('unsupported'); + if ($vars) { + $theme_choices[$f] = $f . ' (' . $vars . ')'; + $theme_choices_mobile[$f] = $f . ' (' . $vars . ')'; + } + else { + $theme_choices[$f] = $f; + $theme_choices_mobile[$f] = $f; + } + } + } + + $dir_choices = null; + $dirmode = get_config('system','directory_mode'); + $realm = get_directory_realm(); + + // directory server should not be set or settable unless we are a directory client + + if($dirmode == DIRECTORY_MODE_NORMAL) { + $x = q("select site_url from site where site_flags in (%d,%d) and site_realm = '%s'", + intval(DIRECTORY_MODE_SECONDARY), + intval(DIRECTORY_MODE_PRIMARY), + dbesc($realm) + ); + if($x) { + $dir_choices = array(); + foreach($x as $xx) { + $dir_choices[$xx['site_url']] = $xx['site_url']; + } + } + } + + /* Banner */ + + $banner = get_config('system', 'banner'); + if($banner === false) + $banner = get_config('system','sitename'); + + $banner = htmlspecialchars($banner); + + /* Admin Info */ + $admininfo = get_config('system', 'admininfo'); + + /* Register policy */ + $register_choices = Array( + REGISTER_CLOSED => t("No"), + REGISTER_APPROVE => t("Yes - with approval"), + REGISTER_OPEN => t("Yes") + ); + + /* Acess policy */ + $access_choices = Array( + ACCESS_PRIVATE => t("My site is not a public server"), + ACCESS_PAID => t("My site has paid access only"), + ACCESS_FREE => t("My site has free access only"), + ACCESS_TIERED => t("My site offers free accounts with optional paid upgrades") + ); + + // $ssl_choices = array( + // SSL_POLICY_NONE => t("No SSL policy, links will track page SSL state"), + // SSL_POLICY_FULL => t("Force all links to use SSL") + // ); + + $discover_tab = get_config('system','disable_discover_tab'); + // $disable public streams by default + if($discover_tab === false) + $discover_tab = 1; + // now invert the logic for the setting. + $discover_tab = (1 - $discover_tab); + + + $homelogin = get_config('system','login_on_homepage'); + $enable_context_help = get_config('system','enable_context_help'); + + $t = get_markup_template("admin_site.tpl"); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Site'), + '$submit' => t('Submit'), + '$registration' => t('Registration'), + '$upload' => t('File upload'), + '$corporate' => t('Policies'), + '$advanced' => t('Advanced'), + + '$baseurl' => z_root(), + // name, label, value, help string, extra data... + '$sitename' => array('sitename', t("Site name"), htmlspecialchars(get_config('system','sitename'), ENT_QUOTES, 'UTF-8'),''), + '$banner' => array('banner', t("Banner/Logo"), $banner, ""), + '$admininfo' => array('admininfo', t("Administrator Information"), $admininfo, t("Contact information for site administrators. Displayed on siteinfo page. BBCode can be used here")), + '$language' => array('language', t("System language"), get_config('system','language'), "", $lang_choices), + '$theme' => array('theme', t("System theme"), get_config('system','theme'), t("Default system theme - may be over-ridden by user profiles - change theme settings"), $theme_choices), + '$theme_mobile' => array('theme_mobile', t("Mobile system theme"), get_config('system','mobile_theme'), t("Theme for mobile devices"), $theme_choices_mobile), + // '$site_channel' => array('site_channel', t("Channel to use for this website's static pages"), get_config('system','site_channel'), t("Site Channel")), + '$feed_contacts' => array('feed_contacts', t('Allow Feeds as Connections'),get_config('system','feed_contacts'),t('(Heavy system resource usage)')), + '$maximagesize' => array('maximagesize', t("Maximum image size"), intval(get_config('system','maximagesize')), t("Maximum size in bytes of uploaded images. Default is 0, which means no limits.")), + '$register_policy' => array('register_policy', t("Does this site allow new member registration?"), get_config('system','register_policy'), "", $register_choices), + '$invite_only' => array('invite_only', t("Invitation only"), get_config('system','invitation_only'), t("Only allow new member registrations with an invitation code. Above register policy must be set to Yes.")), + '$access_policy' => array('access_policy', t("Which best describes the types of account offered by this hub?"), get_config('system','access_policy'), "This is displayed on the public server site list.", $access_choices), + '$register_text' => array('register_text', t("Register text"), htmlspecialchars(get_config('system','register_text'), ENT_QUOTES, 'UTF-8'), t("Will be displayed prominently on the registration page.")), + '$frontpage' => array('frontpage', t("Site homepage to show visitors (default: login box)"), get_config('system','frontpage'), t("example: 'public' to show public stream, 'page/sys/home' to show a system webpage called 'home' or 'include:home.html' to include a file.")), + '$mirror_frontpage' => array('mirror_frontpage', t("Preserve site homepage URL"), get_config('system','mirror_frontpage'), t('Present the site homepage in a frame at the original location instead of redirecting')), + '$abandon_days' => array('abandon_days', t('Accounts abandoned after x days'), get_config('system','account_abandon_days'), t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.')), + '$allowed_sites' => array('allowed_sites', t("Allowed friend domains"), get_config('system','allowed_sites'), t("Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains")), + '$allowed_email' => array('allowed_email', t("Allowed email domains"), get_config('system','allowed_email'), t("Comma separated list of domains which are allowed in email addresses for registrations to this site. Wildcards are accepted. Empty to allow any domains")), + '$not_allowed_email' => array('not_allowed_email', t("Not allowed email domains"), get_config('system','not_allowed_email'), t("Comma separated list of domains which are not allowed in email addresses for registrations to this site. Wildcards are accepted. Empty to allow any domains, unless allowed domains have been defined.")), + '$verify_email' => array('verify_email', t("Verify Email Addresses"), get_config('system','verify_email'), t("Check to verify email addresses used in account registration (recommended).")), + '$force_publish' => array('publish_all', t("Force publish"), get_config('system','publish_all'), t("Check to force all profiles on this site to be listed in the site directory.")), + '$disable_discover_tab' => array('disable_discover_tab', t('Import Public Streams'), $discover_tab, t('Import and allow access to public content pulled from other sites. Warning: this content is unmoderated.')), + '$login_on_homepage' => array('login_on_homepage', t("Login on Homepage"),((intval($homelogin) || $homelogin === false) ? 1 : '') , t("Present a login box to visitors on the home page if no other content has been configured.")), + '$enable_context_help' => array('enable_context_help', t("Enable context help"),((intval($enable_context_help) === 1 || $enable_context_help === false) ? 1 : 0) , t("Display contextual help for the current page when the help button is pressed.")), + + '$directory_server' => (($dir_choices) ? array('directory_server', t("Directory Server URL"), get_config('system','directory_server'), t("Default directory server"), $dir_choices) : null), + + '$proxyuser' => array('proxyuser', t("Proxy user"), get_config('system','proxyuser'), ""), + '$proxy' => array('proxy', t("Proxy URL"), get_config('system','proxy'), ""), + '$timeout' => array('timeout', t("Network timeout"), (x(get_config('system','curl_timeout'))?get_config('system','curl_timeout'):60), t("Value is in seconds. Set to 0 for unlimited (not recommended).")), + '$delivery_interval' => array('delivery_interval', t("Delivery interval"), (x(get_config('system','delivery_interval'))?get_config('system','delivery_interval'):2), t("Delay background delivery processes by this many seconds to reduce system load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 for large dedicated servers.")), + '$delivery_batch_count' => array('delivery_batch_count', t('Deliveries per process'),(x(get_config('system','delivery_batch_count'))?get_config('system','delivery_batch_count'):1), t("Number of deliveries to attempt in a single operating system process. Adjust if necessary to tune system performance. Recommend: 1-5.")), + '$poll_interval' => array('poll_interval', t("Poll interval"), (x(get_config('system','poll_interval'))?get_config('system','poll_interval'):2), t("Delay background polling processes by this many seconds to reduce system load. If 0, use delivery interval.")), + '$maxloadavg' => array('maxloadavg', t("Maximum Load Average"), ((intval(get_config('system','maxloadavg')) > 0)?get_config('system','maxloadavg'):50), t("Maximum system load before delivery and poll processes are deferred - default 50.")), + '$default_expire_days' => array('default_expire_days', t('Expiration period in days for imported (grid/network) content'), intval(get_config('system','default_expire_days')), t('0 for no expiration of imported content')), + '$form_security_token' => get_form_security_token("admin_site"), + )); + } + + function admin_page_hubloc_post(&$a){ + check_form_security_token_redirectOnErr('/admin/hubloc', 'admin_hubloc'); + require_once('include/zot.php'); + + //prepare for ping + + if ( $_POST['hublocid']) { + $hublocid = $_POST['hublocid']; + $arrhublocurl = q("SELECT hubloc_url FROM hubloc WHERE hubloc_id = %d ", + intval($hublocid) + ); + $hublocurl = $arrhublocurl[0]['hubloc_url'] . '/post'; + + //perform ping + $m = zot_build_packet(\App::get_channel(),'ping'); + $r = zot_zot($hublocurl,$m); + //handle results and set the hubloc flags in db to make results visible + $r2 = $r['body']; + $r3 = $r2['success']; + if ( $r3['success'] == True ){ + //set HUBLOC_OFFLINE to 0 + logger(' success = true ',LOGGER_DEBUG); + } else { + //set HUBLOC_OFFLINE to 1 + logger(' success = false ', LOGGER_DEBUG); + } + + //unfotunatly zping wont work, I guess return format is not correct + //require_once('mod/zping.php'); + //$r = zping_content($hublocurl); + //logger('zping answer: ' . $r, LOGGER_DEBUG); + + //in case of repair store new pub key for tested hubloc (all channel with this hubloc) in db + //after repair set hubloc flags to 0 + } + + goaway(z_root() . '/admin/hubloc' ); + } + + function trim_array_elems($arr) { + $narr = array(); + + if($arr && is_array($arr)) { + for($x = 0; $x < count($arr); $x ++) { + $y = trim($arr[$x]); + if($y) + $narr[] = $y; + } + } + return $narr; + } + + function admin_page_security_post(&$a){ + check_form_security_token_redirectOnErr('/admin/security', 'admin_security'); + + logger('post: ' . print_r($_POST,true)); + + $block_public = ((x($_POST,'block_public')) ? True : False); + set_config('system','block_public',$block_public); + + $ws = $this->trim_array_elems(explode("\n",$_POST['whitelisted_sites'])); + set_config('system','whitelisted_sites',$ws); + + $bs = $this->trim_array_elems(explode("\n",$_POST['blacklisted_sites'])); + set_config('system','blacklisted_sites',$bs); + + $wc = $this->trim_array_elems(explode("\n",$_POST['whitelisted_channels'])); + set_config('system','whitelisted_channels',$wc); + + $bc = $this->trim_array_elems(explode("\n",$_POST['blacklisted_channels'])); + set_config('system','blacklisted_channels',$bc); + + $embed_sslonly = ((x($_POST,'embed_sslonly')) ? True : False); + set_config('system','embed_sslonly',$embed_sslonly); + + $we = $this->trim_array_elems(explode("\n",$_POST['embed_allow'])); + set_config('system','embed_allow',$we); + + $be = $this->trim_array_elems(explode("\n",$_POST['embed_deny'])); + set_config('system','embed_deny',$be); + + $ts = ((x($_POST,'transport_security')) ? True : False); + set_config('system','transport_security_header',$ts); + + $cs = ((x($_POST,'content_security')) ? True : False); + set_config('system','content_security_policy',$cs); + + goaway(z_root() . '/admin/security'); + } + + + + + function admin_page_features_post(&$a) { + + check_form_security_token_redirectOnErr('/admin/features', 'admin_manage_features'); + + logger('postvars: ' . print_r($_POST,true)); + + $arr = array(); + $features = get_features(false); + + foreach($features as $fname => $fdata) { + foreach(array_slice($fdata,1) as $f) { + $feature = $f[0]; + + if(array_key_exists('feature_' . $feature,$_POST)) + $val = intval($_POST['feature_' . $feature]); + else + $val = 0; + set_config('feature',$feature,$val); + + if(array_key_exists('featurelock_' . $feature,$_POST)) + set_config('feature_lock',$feature,$val); + else + del_config('feature_lock',$feature); + } + } + + goaway(z_root() . '/admin/features' ); + + } + + function admin_page_features(&$a) { + + if((argc() > 1) && (argv(1) === 'features')) { + $arr = array(); + $features = get_features(false); + + foreach($features as $fname => $fdata) { + $arr[$fname] = array(); + $arr[$fname][0] = $fdata[0]; + foreach(array_slice($fdata,1) as $f) { + + $set = get_config('feature',$f[0]); + if($set === false) + $set = $f[3]; + $arr[$fname][1][] = array( + array('feature_' .$f[0],$f[1],$set,$f[2],array(t('Off'),t('On'))), + array('featurelock_' .$f[0],sprintf( t('Lock feature %s'),$f[1]),(($f[4] !== false) ? 1 : 0),'',array(t('Off'),t('On'))) + ); + } + } + + $tpl = get_markup_template("admin_settings_features.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("admin_manage_features"), + '$title' => t('Manage Additional Features'), + '$features' => $arr, + '$submit' => t('Submit'), + )); + + return $o; + } + } + + + + + + function admin_page_hubloc(&$a) { + $hubloc = q("SELECT hubloc_id, hubloc_addr, hubloc_host, hubloc_status FROM hubloc"); + + if(! $hubloc){ + notice( t('No server found') . EOL); + goaway(z_root() . '/admin/hubloc'); + } + + $t = get_markup_template('admin_hubloc.tpl'); + return replace_macros($t, array( + '$hubloc' => $hubloc, + '$th_hubloc' => array(t('ID'), t('for channel'), t('on server'), t('Status')), + '$title' => t('Administration'), + '$page' => t('Server'), + '$queues' => $queues, + //'$accounts' => $accounts, /*$accounts is empty here*/ + '$pending' => array( t('Pending registrations'), $pending), + '$plugins' => array( t('Active plugins'), \App::$plugins ), + '$form_security_token' => get_form_security_token('admin_hubloc') + )); + } + + function admin_page_security(&$a) { + + $whitesites = get_config('system','whitelisted_sites'); + $whitesites_str = ((is_array($whitesites)) ? implode($whitesites,"\n") : ''); + + $blacksites = get_config('system','blacklisted_sites'); + $blacksites_str = ((is_array($blacksites)) ? implode($blacksites,"\n") : ''); + + + $whitechannels = get_config('system','whitelisted_channels'); + $whitechannels_str = ((is_array($whitechannels)) ? implode($whitechannels,"\n") : ''); + + $blackchannels = get_config('system','blacklisted_channels'); + $blackchannels_str = ((is_array($blackchannels)) ? implode($blackchannels,"\n") : ''); + + + $whiteembeds = get_config('system','embed_allow'); + $whiteembeds_str = ((is_array($whiteembeds)) ? implode($whiteembeds,"\n") : ''); + + $blackembeds = get_config('system','embed_deny'); + $blackembeds_str = ((is_array($blackembeds)) ? implode($blackembeds,"\n") : ''); + + $embed_coop = intval(get_config('system','embed_coop')); + + if((! $whiteembeds) && (! $blackembeds)) { + $embedhelp1 = t("By default, unfiltered HTML is allowed in embedded media. This is inherently insecure."); + } + + $embedhelp2 = t("The recommended setting is to only allow unfiltered HTML from the following sites:"); + $embedhelp3 = t("https://youtube.com/
https://www.youtube.com/
https://youtu.be/
https://vimeo.com/
https://soundcloud.com/
"); + $embedhelp4 = t("All other embedded content will be filtered, unless embedded content from that site is explicitly blocked."); + + $t = get_markup_template('admin_security.tpl'); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Security'), + '$form_security_token' => get_form_security_token('admin_security'), + '$block_public' => array('block_public', t("Block public"), get_config('system','block_public'), t("Check to block public access to all otherwise public personal pages on this site unless you are currently authenticated.")), + '$transport_security' => array('transport_security', t('Set "Transport Security" HTTP header'),intval(get_config('system','transport_security_header')),''), + '$content_security' => array('content_security', t('Set "Content Security Policy" HTTP header'),intval(get_config('system','content_security_policy')),''), + '$whitelisted_sites' => array('whitelisted_sites', t('Allow communications only from these sites'), $whitesites_str, t('One site per line. Leave empty to allow communication from anywhere by default')), + '$blacklisted_sites' => array('blacklisted_sites', t('Block communications from these sites'), $blacksites_str, ''), + '$whitelisted_channels' => array('whitelisted_channels', t('Allow communications only from these channels'), $whitechannels_str, t('One channel (hash) per line. Leave empty to allow from any channel by default')), + '$blacklisted_channels' => array('blacklisted_channels', t('Block communications from these channels'), $blackchannels_str, ''), + '$embed_sslonly' => array('embed_sslonly',t('Only allow embeds from secure (SSL) websites and links.'), intval(get_config('system','embed_sslonly')),''), + '$embed_allow' => array('embed_allow', t('Allow unfiltered embedded HTML content only from these domains'), $whiteembeds_str, t('One site per line. By default embedded content is filtered.')), + '$embed_deny' => array('embed_deny', t('Block embedded HTML from these domains'), $blackembeds_str, ''), + +// '$embed_coop' => array('embed_coop', t('Cooperative embed security'), $embed_coop, t('Enable to share embed security with other compatible sites/hubs')), + + '$submit' => t('Submit') + )); + } + + + + + function admin_page_dbsync(&$a) { + $o = ''; + + if(argc() > 3 && intval(argv(3)) && argv(2) === 'mark') { + set_config('database', 'update_r' . intval(argv(3)), 'success'); + if(intval(get_config('system','db_version')) <= intval(argv(3))) + set_config('system','db_version',intval(argv(3)) + 1); + info( t('Update has been marked successful') . EOL); + goaway(z_root() . '/admin/dbsync'); + } + + if(argc() > 2 && intval(argv(2))) { + require_once('install/update.php'); + $func = 'update_r' . intval(argv(2)); + if(function_exists($func)) { + $retval = $func(); + if($retval === UPDATE_FAILED) { + $o .= sprintf( t('Executing %s failed. Check system logs.'), $func); + } + elseif($retval === UPDATE_SUCCESS) { + $o .= sprintf( t('Update %s was successfully applied.'), $func); + set_config('database',$func, 'success'); + } + else + $o .= sprintf( t('Update %s did not return a status. Unknown if it succeeded.'), $func); + } + else + $o .= sprintf( t('Update function %s could not be found.'), $func); + + return $o; + } + + $failed = array(); + $r = q("select * from config where `cat` = 'database' "); + if(count($r)) { + foreach($r as $rr) { + $upd = intval(substr($rr['k'],8)); + if($rr['v'] === 'success') + continue; + $failed[] = $upd; + } + } + if(! count($failed)) + return '

' . t('No failed updates.') . '

'; + + $o = replace_macros(get_markup_template('failed_updates.tpl'),array( + '$base' => z_root(), + '$banner' => t('Failed Updates'), + '$desc' => '', + '$mark' => t('Mark success (if update was manually applied)'), + '$apply' => t('Attempt to execute this update step automatically'), + '$failed' => $failed + )); + + return $o; + } + + function admin_page_queue($a) { + $o = ''; + + $expert = ((array_key_exists('expert',$_REQUEST)) ? intval($_REQUEST['expert']) : 0); + + if($_REQUEST['drophub']) { + require_once('hubloc.php'); + hubloc_mark_as_down($_REQUEST['drophub']); + remove_queue_by_posturl($_REQUEST['drophub']); + } + + if($_REQUEST['emptyhub']) { + remove_queue_by_posturl($_REQUEST['emptyhub']); + } + + $r = q("select count(outq_posturl) as total, max(outq_priority) as priority, outq_posturl from outq + where outq_delivered = 0 group by outq_posturl order by total desc"); + + for($x = 0; $x < count($r); $x ++) { + $r[$x]['eurl'] = urlencode($r[$x]['outq_posturl']); + $r[$x]['connected'] = datetime_convert('UTC',date_default_timezone_get(),$r[$x]['connected'],'Y-m-d'); + } + + $o = replace_macros(get_markup_template('admin_queue.tpl'), array( + '$banner' => t('Queue Statistics'), + '$numentries' => t('Total Entries'), + '$priority' => t('Priority'), + '$desturl' => t('Destination URL'), + '$nukehub' => t('Mark hub permanently offline'), + '$empty' => t('Empty queue for this hub'), + '$lastconn' => t('Last known contact'), + '$hasentries' => ((count($r)) ? true : false), + '$entries' => $r, + '$expert' => $expert + )); + + return $o; + } + + /** + * @brief Handle POST actions on accounts admin page. + * + * This function is called when on the admin user/account page the form was + * submitted to handle multiple operations at once. If one of the icons next + * to an entry are pressed the function admin_page_accounts() will handle this. + * + * @param App $a + */ + function admin_page_accounts_post($a) { + $pending = ( x($_POST, 'pending') ? $_POST['pending'] : array() ); + $users = ( x($_POST, 'user') ? $_POST['user'] : array() ); + $blocked = ( x($_POST, 'blocked') ? $_POST['blocked'] : array() ); + + check_form_security_token_redirectOnErr('/admin/accounts', 'admin_accounts'); + + // change to switch structure? + // account block/unblock button was submitted + if (x($_POST, 'page_users_block')) { + for ($i = 0; $i < count($users); $i++) { + // if account is blocked remove blocked bit-flag, otherwise add blocked bit-flag + $op = ($blocked[$i]) ? '& ~' : '| '; + q("UPDATE account SET account_flags = (account_flags $op%d) WHERE account_id = %d", + intval(ACCOUNT_BLOCKED), + intval($users[$i]) + ); + } + notice( sprintf( tt("%s account blocked/unblocked", "%s account blocked/unblocked", count($users)), count($users)) ); + } + // account delete button was submitted + if (x($_POST, 'page_accounts_delete')) { + foreach ($users as $uid){ + account_remove($uid, true, false); + } + notice( sprintf( tt("%s account deleted", "%s accounts deleted", count($users)), count($users)) ); + } + // registration approved button was submitted + if (x($_POST, 'page_users_approve')) { + foreach ($pending as $hash) { + account_allow($hash); + } + } + // registration deny button was submitted + if (x($_POST, 'page_users_deny')) { + foreach ($pending as $hash) { + account_deny($hash); + } + } + + goaway(z_root() . '/admin/accounts' ); + } + + /** + * @brief Generate accounts admin page and handle single item operations. + * + * This function generates the accounts/account admin page and handles the actions + * if an icon next to an entry was clicked. If several items were selected and + * the form was submitted it is handled by the function admin_page_accounts_post(). + * + * @param App &$a + * @return string + */ + function admin_page_accounts(&$a){ + if (argc() > 2) { + $uid = argv(3); + $account = q("SELECT * FROM account WHERE account_id = %d", + intval($uid) + ); + + if (! $account) { + notice( t('Account not found') . EOL); + goaway(z_root() . '/admin/accounts' ); + } + + check_form_security_token_redirectOnErr('/admin/accounts', 'admin_accounts', 't'); + + switch (argv(2)){ + case 'delete': + // delete user + account_remove($uid,true,false); + + notice( sprintf(t("Account '%s' deleted"), $account[0]['account_email']) . EOL); + break; + case 'block': + q("UPDATE account SET account_flags = ( account_flags | %d ) WHERE account_id = %d", + intval(ACCOUNT_BLOCKED), + intval($uid) + ); + + notice( sprintf( t("Account '%s' blocked") , $account[0]['account_email']) . EOL); + break; + case 'unblock': + q("UPDATE account SET account_flags = ( account_flags & ~%d ) WHERE account_id = %d", + intval(ACCOUNT_BLOCKED), + intval($uid) + ); + + notice( sprintf( t("Account '%s' unblocked"), $account[0]['account_email']) . EOL); + break; + } + + goaway(z_root() . '/admin/accounts' ); + } + + /* get pending */ + $pending = q("SELECT account.*, register.hash from account left join register on account_id = register.uid where (account_flags & %d )>0 ", + intval(ACCOUNT_PENDING) + ); + + /* get accounts */ + + $total = q("SELECT count(*) as total FROM account"); + if (count($total)) { + \App::set_pager_total($total[0]['total']); + \App::set_pager_itemspage(100); + } + + $serviceclass = (($_REQUEST['class']) ? " and account_service_class = '" . dbesc($_REQUEST['class']) . "' " : ''); + + $key = (($_REQUEST['key']) ? dbesc($_REQUEST['key']) : 'account_id'); + $dir = 'asc'; + if(array_key_exists('dir',$_REQUEST)) + $dir = ((intval($_REQUEST['dir'])) ? 'asc' : 'desc'); + + $base = z_root() . '/admin/accounts?f='; + $odir = (($dir === 'asc') ? '0' : '1'); + + $users = q("SELECT `account_id` , `account_email`, `account_lastlog`, `account_created`, `account_expires`, " . "`account_service_class`, ( account_flags & %d ) > 0 as `blocked`, " . + "(SELECT %s FROM channel as ch " . + "WHERE ch.channel_account_id = ac.account_id and ch.channel_removed = 0 ) as `channels` " . + "FROM account as ac where true $serviceclass order by $key $dir limit %d offset %d ", + intval(ACCOUNT_BLOCKED), + db_concat('ch.channel_address', ' '), + intval(\App::$pager['itemspage']), + intval(\App::$pager['start']) + ); + + // function _setup_users($e){ + // $accounts = Array( + // t('Normal Account'), + // t('Soapbox Account'), + // t('Community/Celebrity Account'), + // t('Automatic Friend Account') + // ); + + // $e['page_flags'] = $accounts[$e['page-flags']]; + // $e['register_date'] = relative_date($e['register_date']); + // $e['login_date'] = relative_date($e['login_date']); + // $e['lastitem_date'] = relative_date($e['lastitem_date']); + // return $e; + // } + // $users = array_map("_setup_users", $users); + + $t = get_markup_template('admin_accounts.tpl'); + $o = replace_macros($t, array( + // strings // + '$title' => t('Administration'), + '$page' => t('Accounts'), + '$submit' => t('Submit'), + '$select_all' => t('select all'), + '$h_pending' => t('Registrations waiting for confirm'), + '$th_pending' => array( t('Request date'), t('Email') ), + '$no_pending' => t('No registrations.'), + '$approve' => t('Approve'), + '$deny' => t('Deny'), + '$delete' => t('Delete'), + '$block' => t('Block'), + '$unblock' => t('Unblock'), + '$odir' => $odir, + '$base' => $base, + '$h_users' => t('Accounts'), + '$th_users' => array( + [ t('ID'), 'account_id' ], + [ t('Email'), 'account_email' ], + [ t('All Channels'), 'channels' ], + [ t('Register date'), 'account_created' ], + [ t('Last login'), 'account_lastlog' ], + [ t('Expires'), 'account_expires' ], + [ t('Service Class'), 'account_service_class'] ), + + '$confirm_delete_multi' => t('Selected accounts will be deleted!\n\nEverything these accounts had posted on this site will be permanently deleted!\n\nAre you sure?'), + '$confirm_delete' => t('The account {0} will be deleted!\n\nEverything this account has posted on this site will be permanently deleted!\n\nAre you sure?'), + + '$form_security_token' => get_form_security_token("admin_accounts"), + + // values // + '$baseurl' => z_root(), + + '$pending' => $pending, + '$users' => $users, + )); + $o .= paginate($a); + + return $o; + } + + + /** + * @brief Channels admin page. + * + * @param App &$a + */ + function admin_page_channels_post(&$a) { + $channels = ( x($_POST, 'channel') ? $_POST['channel'] : Array() ); + + check_form_security_token_redirectOnErr('/admin/channels', 'admin_channels'); + + $xor = db_getfunc('^'); + + if (x($_POST,'page_channels_block')){ + foreach($channels as $uid){ + q("UPDATE channel SET channel_pageflags = ( channel_pageflags $xor %d ) where channel_id = %d", + intval(PAGE_CENSORED), + intval( $uid ) + ); + \Zotlabs\Daemon\Master::Summon(array('Directory',$uid,'nopush')); + } + notice( sprintf( tt("%s channel censored/uncensored", "%s channels censored/uncensored", count($channels)), count($channels)) ); + } + if (x($_POST,'page_channels_code')){ + foreach($channels as $uid){ + q("UPDATE channel SET channel_pageflags = ( channel_pageflags $xor %d ) where channel_id = %d", + intval(PAGE_ALLOWCODE), + intval( $uid ) + ); + } + notice( sprintf( tt("%s channel code allowed/disallowed", "%s channels code allowed/disallowed", count($channels)), count($channels)) ); + } + if (x($_POST,'page_channels_delete')){ + foreach($channels as $uid){ + channel_remove($uid,true); + } + notice( sprintf( tt("%s channel deleted", "%s channels deleted", count($channels)), count($channels)) ); + } + + goaway(z_root() . '/admin/channels' ); + } + + /** + * @brief + * + * @param App &$a + * @return string + */ + function admin_page_channels(&$a){ + if (argc() > 2) { + $uid = argv(3); + $channel = q("SELECT * FROM channel WHERE channel_id = %d", + intval($uid) + ); + + if (! $channel) { + notice( t('Channel not found') . EOL); + goaway(z_root() . '/admin/channels' ); + } + + switch(argv(2)) { + case "delete":{ + check_form_security_token_redirectOnErr('/admin/channels', 'admin_channels', 't'); + // delete channel + channel_remove($uid,true); + + notice( sprintf(t("Channel '%s' deleted"), $channel[0]['channel_name']) . EOL); + }; break; + + case "block":{ + check_form_security_token_redirectOnErr('/admin/channels', 'admin_channels', 't'); + $pflags = $channel[0]['channel_pageflags'] ^ PAGE_CENSORED; + q("UPDATE channel SET channel_pageflags = %d where channel_id = %d", + intval($pflags), + intval( $uid ) + ); + \Zotlabs\Daemon\Master::Summon(array('Directory',$uid,'nopush')); + + notice( sprintf( (($pflags & PAGE_CENSORED) ? t("Channel '%s' censored"): t("Channel '%s' uncensored")) , $channel[0]['channel_name'] . ' (' . $channel[0]['channel_address'] . ')' ) . EOL); + }; break; + + case "code":{ + check_form_security_token_redirectOnErr('/admin/channels', 'admin_channels', 't'); + $pflags = $channel[0]['channel_pageflags'] ^ PAGE_ALLOWCODE; + q("UPDATE channel SET channel_pageflags = %d where channel_id = %d", + intval($pflags), + intval( $uid ) + ); + + notice( sprintf( (($pflags & PAGE_ALLOWCODE) ? t("Channel '%s' code allowed"): t("Channel '%s' code disallowed")) , $channel[0]['channel_name'] . ' (' . $channel[0]['channel_address'] . ')' ) . EOL); + }; break; + + default: + break; + } + goaway(z_root() . '/admin/channels' ); + } + + + $key = (($_REQUEST['key']) ? dbesc($_REQUEST['key']) : 'channel_id'); + $dir = 'asc'; + if(array_key_exists('dir',$_REQUEST)) + $dir = ((intval($_REQUEST['dir'])) ? 'asc' : 'desc'); + + $base = z_root() . '/admin/channels?f='; + $odir = (($dir === 'asc') ? '0' : '1'); + + + + /* get channels */ + + $total = q("SELECT count(*) as total FROM channel where channel_removed = 0 and channel_system = 0"); + if($total) { + \App::set_pager_total($total[0]['total']); + \App::set_pager_itemspage(100); + } + + $channels = q("SELECT * from channel where channel_removed = 0 and channel_system = 0 order by $key $dir limit %d offset %d ", + intval(\App::$pager['itemspage']), + intval(\App::$pager['start']) + ); + + if($channels) { + for($x = 0; $x < count($channels); $x ++) { + if($channels[$x]['channel_pageflags'] & PAGE_CENSORED) + $channels[$x]['blocked'] = true; + else + $channels[$x]['blocked'] = false; + + if($channels[$x]['channel_pageflags'] & PAGE_ALLOWCODE) + $channels[$x]['allowcode'] = true; + else + $channels[$x]['allowcode'] = false; + } + } + + $t = get_markup_template("admin_channels.tpl"); + $o = replace_macros($t, array( + // strings // + '$title' => t('Administration'), + '$page' => t('Channels'), + '$submit' => t('Submit'), + '$select_all' => t('select all'), + '$delete' => t('Delete'), + '$block' => t('Censor'), + '$unblock' => t('Uncensor'), + '$code' => t('Allow Code'), + '$uncode' => t('Disallow Code'), + '$h_channels' => t('Channel'), + '$base' => $base, + '$odir' => $odir, + '$th_channels' => array( + [ t('UID'), 'channel_id' ], + [ t('Name'), 'channel_name' ], + [ t('Address'), 'channel_address' ]), + + '$confirm_delete_multi' => t('Selected channels will be deleted!\n\nEverything that was posted in these channels on this site will be permanently deleted!\n\nAre you sure?'), + '$confirm_delete' => t('The channel {0} will be deleted!\n\nEverything that was posted in this channel on this site will be permanently deleted!\n\nAre you sure?'), + + '$form_security_token' => get_form_security_token("admin_channels"), + + // values // + '$baseurl' => z_root(), + '$channels' => $channels, + )); + $o .= paginate($a); + + return $o; + } + + + /** + * Plugins admin page + * + * @param App $a + * @return string + */ + function admin_page_plugins(&$a){ + + /* + * Single plugin + */ + if (\App::$argc == 3){ + $plugin = \App::$argv[2]; + if (!is_file("addon/$plugin/$plugin.php")){ + notice( t("Item not found.") ); + return ''; + } + + $enabled = in_array($plugin,\App::$plugins); + $info = get_plugin_info($plugin); + $x = check_plugin_versions($info); + + // disable plugins which are installed but incompatible versions + + if($enabled && ! $x) { + $enabled = false; + $idz = array_search($plugin, \App::$plugins); + if ($idz !== false) { + unset(\App::$plugins[$idz]); + uninstall_plugin($plugin); + set_config("system","addon", implode(", ",\App::$plugins)); + } + } + $info['disabled'] = 1-intval($x); + + if (x($_GET,"a") && $_GET['a']=="t"){ + check_form_security_token_redirectOnErr('/admin/plugins', 'admin_plugins', 't'); + + // Toggle plugin status + $idx = array_search($plugin, \App::$plugins); + if ($idx !== false){ + unset(\App::$plugins[$idx]); + uninstall_plugin($plugin); + info( sprintf( t("Plugin %s disabled."), $plugin ) ); + } else { + \App::$plugins[] = $plugin; + install_plugin($plugin); + info( sprintf( t("Plugin %s enabled."), $plugin ) ); + } + set_config("system","addon", implode(", ",\App::$plugins)); + goaway(z_root() . '/admin/plugins' ); + } + // display plugin details + require_once('library/markdown.php'); + + if (in_array($plugin, \App::$plugins)){ + $status = 'on'; + $action = t('Disable'); + } else { + $status = 'off'; + $action = t('Enable'); + } + + $readme = null; + if (is_file("addon/$plugin/README.md")){ + $readme = file_get_contents("addon/$plugin/README.md"); + $readme = Markdown($readme); + } else if (is_file("addon/$plugin/README")){ + $readme = "
". file_get_contents("addon/$plugin/README") ."
"; + } + + $admin_form = ''; + + $r = q("select * from addon where plugin_admin = 1 and aname = '%s' limit 1", + dbesc($plugin) + ); + + if($r) { + @require_once("addon/$plugin/$plugin.php"); + if(function_exists($plugin.'_plugin_admin')) { + $func = $plugin.'_plugin_admin'; + $func($a, $admin_form); + } + } + + + $t = get_markup_template('admin_plugins_details.tpl'); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Plugins'), + '$toggle' => t('Toggle'), + '$settings' => t('Settings'), + '$baseurl' => z_root(), + + '$plugin' => $plugin, + '$status' => $status, + '$action' => $action, + '$info' => $info, + '$str_author' => t('Author: '), + '$str_maintainer' => t('Maintainer: '), + '$str_minversion' => t('Minimum project version: '), + '$str_maxversion' => t('Maximum project version: '), + '$str_minphpversion' => t('Minimum PHP version: '), + '$str_requires' => t('Requires: '), + '$disabled' => t('Disabled - version incompatibility'), + + '$admin_form' => $admin_form, + '$function' => 'plugins', + '$screenshot' => '', + '$readme' => $readme, + + '$form_security_token' => get_form_security_token('admin_plugins'), + )); + } + + + /* + * List plugins + */ + $plugins = array(); + $files = glob('addon/*/'); + if($files) { + foreach($files as $file) { + if (is_dir($file)){ + list($tmp, $id) = array_map('trim', explode('/', $file)); + $info = get_plugin_info($id); + $enabled = in_array($id,\App::$plugins); + $x = check_plugin_versions($info); + + // disable plugins which are installed but incompatible versions + + if($enabled && ! $x) { + $enabled = false; + $idz = array_search($id, \App::$plugins); + if ($idz !== false) { + unset(\App::$plugins[$idz]); + uninstall_plugin($id); + set_config("system","addon", implode(", ",\App::$plugins)); + } + } + $info['disabled'] = 1-intval($x); + + $plugins[] = array( $id, (($enabled)?"on":"off") , $info); + } + } + } + + usort($plugins,'self::plugin_sort'); + + + $admin_plugins_add_repo_form= replace_macros( + get_markup_template('admin_plugins_addrepo.tpl'), array( + '$post' => 'admin/plugins/addrepo', + '$desc' => t('Enter the public git repository URL of the plugin repo.'), + '$repoURL' => array('repoURL', t('Plugin repo git URL'), '', ''), + '$repoName' => array('repoName', t('Custom repo name'), '', '', t('(optional)')), + '$submit' => t('Download Plugin Repo') + ) + ); + $newRepoModalID = random_string(3); + $newRepoModal = replace_macros( + get_markup_template('generic_modal.tpl'), array( + '$id' => $newRepoModalID, + '$title' => t('Install new repo'), + '$ok' => t('Install'), + '$cancel' => t('Cancel') + ) + ); + + $reponames = $this->listAddonRepos(); + $addonrepos = []; + foreach($reponames as $repo) { + $addonrepos[] = array('name' => $repo, 'description' => ''); + // TODO: Parse repo info to provide more information about repos + } + + $t = get_markup_template('admin_plugins.tpl'); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Plugins'), + '$submit' => t('Submit'), + '$baseurl' => z_root(), + '$function' => 'plugins', + '$plugins' => $plugins, + '$disabled' => t('Disabled - version incompatibility'), + '$form_security_token' => get_form_security_token('admin_plugins'), + '$managerepos' => t('Manage Repos'), + '$installedtitle' => t('Installed Plugin Repositories'), + '$addnewrepotitle' => t('Install a New Plugin Repository'), + '$expandform' => false, + '$form' => $admin_plugins_add_repo_form, + '$newRepoModal' => $newRepoModal, + '$newRepoModalID' => $newRepoModalID, + '$addonrepos' => $addonrepos, + '$repoUpdateButton' => t('Update'), + '$repoBranchButton' => t('Switch branch'), + '$repoRemoveButton' => t('Remove') + )); + } + + function listAddonRepos() { + $addonrepos = []; + $addonDir = __DIR__ . '/../../extend/addon/'; + if(is_dir($addonDir)) { + if ($handle = opendir($addonDir)) { + while (false !== ($entry = readdir($handle))) { + if ($entry != "." && $entry != "..") { + $addonrepos[] = $entry; + } + } + closedir($handle); + } + } + return $addonrepos; + } + + static public function plugin_sort($a,$b) { + return(strcmp(strtolower($a[2]['name']),strtolower($b[2]['name']))); + } + + + /** + * @param array $themes + * @param string $th + * @param int $result + */ + function toggle_theme(&$themes, $th, &$result) { + for($x = 0; $x < count($themes); $x ++) { + if($themes[$x]['name'] === $th) { + if($themes[$x]['allowed']) { + $themes[$x]['allowed'] = 0; + $result = 0; + } + else { + $themes[$x]['allowed'] = 1; + $result = 1; + } + } + } + } + + /** + * @param array $themes + * @param string $th + * @return int + */ + function theme_status($themes, $th) { + for($x = 0; $x < count($themes); $x ++) { + if($themes[$x]['name'] === $th) { + if($themes[$x]['allowed']) { + return 1; + } + else { + return 0; + } + } + } + return 0; + } + + + /** + * @param array $themes + * @return string + */ + function rebuild_theme_table($themes) { + $o = ''; + if(count($themes)) { + foreach($themes as $th) { + if($th['allowed']) { + if(strlen($o)) + $o .= ','; + $o .= $th['name']; + } + } + } + return $o; + } + + + /** + * @brief Themes admin page. + * + * @param App &$a + * @return string + */ + function admin_page_themes(&$a){ + + $allowed_themes_str = get_config('system', 'allowed_themes'); + $allowed_themes_raw = explode(',', $allowed_themes_str); + $allowed_themes = array(); + if(count($allowed_themes_raw)) + foreach($allowed_themes_raw as $x) + if(strlen(trim($x))) + $allowed_themes[] = trim($x); + + $themes = array(); + $files = glob('view/theme/*'); + if($files) { + foreach($files as $file) { + $f = basename($file); + $is_experimental = intval(file_exists($file . '/.experimental')); + $is_supported = 1-(intval(file_exists($file . '/.unsupported'))); // Is not used yet + $is_allowed = intval(in_array($f,$allowed_themes)); + $themes[] = array('name' => $f, 'experimental' => $is_experimental, 'supported' => $is_supported, 'allowed' => $is_allowed); + } + } + + if(! count($themes)) { + notice( t('No themes found.')); + return ''; + } + + /* + * Single theme + */ + + if (\App::$argc == 3){ + $theme = \App::$argv[2]; + if(! is_dir("view/theme/$theme")){ + notice( t("Item not found.") ); + return ''; + } + + if (x($_GET,"a") && $_GET['a']=="t"){ + check_form_security_token_redirectOnErr('/admin/themes', 'admin_themes', 't'); + + // Toggle theme status + + $this->toggle_theme($themes, $theme, $result); + $s = $this->rebuild_theme_table($themes); + if($result) + info( sprintf('Theme %s enabled.', $theme)); + else + info( sprintf('Theme %s disabled.', $theme)); + + set_config('system', 'allowed_themes', $s); + goaway(z_root() . '/admin/themes' ); + } + + // display theme details + require_once('library/markdown.php'); + + if ($this->theme_status($themes,$theme)) { + $status="on"; $action= t("Disable"); + } else { + $status="off"; $action= t("Enable"); + } + + $readme=Null; + if (is_file("view/theme/$theme/README.md")){ + $readme = file_get_contents("view/theme/$theme/README.md"); + $readme = Markdown($readme); + } else if (is_file("view/theme/$theme/README")){ + $readme = "
". file_get_contents("view/theme/$theme/README") ."
"; + } + + $admin_form = ''; + if (is_file("view/theme/$theme/php/config.php")){ + require_once("view/theme/$theme/php/config.php"); + if(function_exists("theme_admin")){ + $admin_form = theme_admin($a); + } + } + + $screenshot = array( get_theme_screenshot($theme), t('Screenshot')); + if(! stristr($screenshot[0],$theme)) + $screenshot = null; + + $t = get_markup_template('admin_plugins_details.tpl'); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Themes'), + '$toggle' => t('Toggle'), + '$settings' => t('Settings'), + '$baseurl' => z_root(), + + '$plugin' => $theme, + '$status' => $status, + '$action' => $action, + '$info' => get_theme_info($theme), + '$function' => 'themes', + '$admin_form' => $admin_form, + '$str_author' => t('Author: '), + '$str_maintainer' => t('Maintainer: '), + '$screenshot' => $screenshot, + '$readme' => $readme, + + '$form_security_token' => get_form_security_token('admin_themes'), + )); + } + + /* + * List themes + */ + + $xthemes = array(); + if($themes) { + foreach($themes as $th) { + $xthemes[] = array($th['name'],(($th['allowed']) ? "on" : "off"), get_theme_info($th['name'])); + } + } + + $t = get_markup_template('admin_plugins.tpl'); + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Themes'), + '$submit' => t('Submit'), + '$baseurl' => z_root(), + '$function' => 'themes', + '$plugins' => $xthemes, + '$experimental' => t('[Experimental]'), + '$unsupported' => t('[Unsupported]'), + '$form_security_token' => get_form_security_token('admin_themes'), + )); + } + + + /** + * @brief POST handler for logs admin page. + * + * @param App &$a + */ + function admin_page_logs_post(&$a) { + if (x($_POST, 'page_logs')) { + check_form_security_token_redirectOnErr('/admin/logs', 'admin_logs'); + + $logfile = ((x($_POST,'logfile')) ? notags(trim($_POST['logfile'])) : ''); + $debugging = ((x($_POST,'debugging')) ? true : false); + $loglevel = ((x($_POST,'loglevel')) ? intval(trim($_POST['loglevel'])) : 0); + + set_config('system','logfile', $logfile); + set_config('system','debugging', $debugging); + set_config('system','loglevel', $loglevel); + } + + info( t('Log settings updated.') ); + goaway(z_root() . '/admin/logs' ); + } + + /** + * @brief Logs admin page. + * + * @param App $a + * @return string + */ + function admin_page_logs(&$a){ + + $log_choices = Array( + LOGGER_NORMAL => 'Normal', + LOGGER_TRACE => 'Trace', + LOGGER_DEBUG => 'Debug', + LOGGER_DATA => 'Data', + LOGGER_ALL => 'All' + ); + + $t = get_markup_template('admin_logs.tpl'); + + $f = get_config('system', 'logfile'); + + $data = ''; + + if(!file_exists($f)) { + $data = t("Error trying to open $f log file.\r\n
Check to see if file $f exist and is + readable."); + } + else { + $fp = fopen($f, 'r'); + if(!$fp) { + $data = t("Couldn't open $f log file.\r\n
Check to see if file $f is readable."); + } + else { + $fstat = fstat($fp); + $size = $fstat['size']; + if($size != 0) + { + if($size > 5000000 || $size < 0) + $size = 5000000; + $seek = fseek($fp,0-$size,SEEK_END); + if($seek === 0) { + $data = escape_tags(fread($fp,$size)); + while(! feof($fp)) + $data .= escape_tags(fread($fp,4096)); + } + } + fclose($fp); + } + } + + return replace_macros($t, array( + '$title' => t('Administration'), + '$page' => t('Logs'), + '$submit' => t('Submit'), + '$clear' => t('Clear'), + '$data' => $data, + '$baseurl' => z_root(), + '$logname' => get_config('system','logfile'), + + // name, label, value, help string, extra data... + '$debugging' => array('debugging', t("Debugging"),get_config('system','debugging'), ""), + '$logfile' => array('logfile', t("Log file"), get_config('system','logfile'), t("Must be writable by web server. Relative to your top-level webserver directory.")), + '$loglevel' => array('loglevel', t("Log level"), get_config('system','loglevel'), "", $log_choices), + + '$form_security_token' => get_form_security_token('admin_logs'), + )); + } + + function admin_page_plugins_post($action) { + switch ($action) { + case 'updaterepo': + if (array_key_exists('repoName', $_REQUEST)) { + $repoName = $_REQUEST['repoName']; + } else { + json_return_and_die(array('message' => 'No repo name provided.', 'success' => false)); + } + $extendDir = __DIR__ . '/../../store/[data]/git/sys/extend'; + $addonDir = $extendDir . '/addon'; + if (!file_exists($extendDir)) { + if (!mkdir($extendDir, 0770, true)) { + logger('Error creating extend folder: ' . $extendDir); + json_return_and_die(array('message' => 'Error creating extend folder: ' . $extendDir, 'success' => false)); + } else { + if (!symlink(__DIR__ . '/../../extend/addon', $addonDir)) { + logger('Error creating symlink to addon folder: ' . $addonDir); + json_return_and_die(array('message' => 'Error creating symlink to addon folder: ' . $addonDir, 'success' => false)); + } + } + } + $repoDir = __DIR__ . '/../../store/[data]/git/sys/extend/addon/' . $repoName; + if (!is_dir($repoDir)) { + logger('Repo directory does not exist: ' . $repoDir); + json_return_and_die(array('message' => 'Invalid addon repo.', 'success' => false)); + } + if (!is_writable($repoDir)) { + logger('Repo directory not writable to web server: ' . $repoDir); + json_return_and_die(array('message' => 'Repo directory not writable to web server.', 'success' => false)); + } + $git = new GitRepo('sys', null, false, $repoName, $repoDir); + try { + if ($git->pull()) { + $files = array_diff(scandir($repoDir), array('.', '..')); + foreach ($files as $file) { + if (is_dir($repoDir . '/' . $file) && $file !== '.git') { + $source = '../extend/addon/' . $repoName . '/' . $file; + $target = realpath(__DIR__ . '/../../addon/') . '/' . $file; + unlink($target); + if (!symlink($source, $target)) { + logger('Error linking addons to /addon'); + json_return_and_die(array('message' => 'Error linking addons to /addon', 'success' => false)); + } + } + } + json_return_and_die(array('message' => 'Repo updated.', 'success' => true)); + } else { + json_return_and_die(array('message' => 'Error updating addon repo.', 'success' => false)); + } + } catch (\PHPGit\Exception\GitException $e) { + json_return_and_die(array('message' => 'Error updating addon repo.', 'success' => false)); + } + case 'removerepo': + if (array_key_exists('repoName', $_REQUEST)) { + $repoName = $_REQUEST['repoName']; + } else { + json_return_and_die(array('message' => 'No repo name provided.', 'success' => false)); + } + $extendDir = __DIR__ . '/../../store/[data]/git/sys/extend'; + $addonDir = $extendDir . '/addon'; + if (!file_exists($extendDir)) { + if (!mkdir($extendDir, 0770, true)) { + logger('Error creating extend folder: ' . $extendDir); + json_return_and_die(array('message' => 'Error creating extend folder: ' . $extendDir, 'success' => false)); + } else { + if (!symlink(__DIR__ . '/../../extend/addon', $addonDir)) { + logger('Error creating symlink to addon folder: ' . $addonDir); + json_return_and_die(array('message' => 'Error creating symlink to addon folder: ' . $addonDir, 'success' => false)); + } + } + } + $repoDir = __DIR__ . '/../../store/[data]/git/sys/extend/addon/' . $repoName; + if (!is_dir($repoDir)) { + logger('Repo directory does not exist: ' . $repoDir); + json_return_and_die(array('message' => 'Invalid addon repo.', 'success' => false)); + } + if (!is_writable($repoDir)) { + logger('Repo directory not writable to web server: ' . $repoDir); + json_return_and_die(array('message' => 'Repo directory not writable to web server.', 'success' => false)); + } + // TODO: remove directory and unlink /addon/files + if (rrmdir($repoDir)) { + json_return_and_die(array('message' => 'Repo deleted.', 'success' => true)); + } else { + json_return_and_die(array('message' => 'Error deleting addon repo.', 'success' => false)); + } + case 'installrepo': + require_once('library/markdown.php'); + if (array_key_exists('repoURL', $_REQUEST)) { + require __DIR__ . '/../../library/PHPGit.autoload.php'; // Load PHPGit dependencies + $repoURL = $_REQUEST['repoURL']; + $extendDir = __DIR__ . '/../../store/[data]/git/sys/extend'; + $addonDir = $extendDir . '/addon'; + if (!file_exists($extendDir)) { + if (!mkdir($extendDir, 0770, true)) { + logger('Error creating extend folder: ' . $extendDir); + json_return_and_die(array('message' => 'Error creating extend folder: ' . $extendDir, 'success' => false)); + } else { + if (!symlink(__DIR__ . '/../../extend/addon', $addonDir)) { + logger('Error creating symlink to addon folder: ' . $addonDir); + json_return_and_die(array('message' => 'Error creating symlink to addon folder: ' . $addonDir, 'success' => false)); + } + } + } + if (!is_writable($extendDir)) { + logger('Directory not writable to web server: ' . $extendDir); + json_return_and_die(array('message' => 'Directory not writable to web server.', 'success' => false)); + } + $repoName = null; + if (array_key_exists('repoName', $_REQUEST) && $_REQUEST['repoName'] !== '') { + $repoName = $_REQUEST['repoName']; + } else { + $repoName = GitRepo::getRepoNameFromURL($repoURL); + } + if (!$repoName) { + logger('Invalid git repo'); + json_return_and_die(array('message' => 'Invalid git repo', 'success' => false)); + } + $repoDir = $addonDir . '/' . $repoName; + $tempRepoBaseDir = __DIR__ . '/../../store/[data]/git/sys/temp/'; + $tempAddonDir = $tempRepoBaseDir . $repoName; + + if (!is_writable($addonDir) || !is_writable($tempAddonDir)) { + logger('Temp repo directory or /extend/addon not writable to web server: ' . $tempAddonDir); + json_return_and_die(array('message' => 'Temp repo directory not writable to web server.', 'success' => false)); + } + rename($tempAddonDir, $repoDir); + + if (!is_writable(realpath(__DIR__ . '/../../addon/'))) { + logger('/addon directory not writable to web server: ' . $tempAddonDir); + json_return_and_die(array('message' => '/addon directory not writable to web server.', 'success' => false)); + } + $files = array_diff(scandir($repoDir), array('.', '..')); + foreach ($files as $file) { + if (is_dir($repoDir . '/' . $file) && $file !== '.git') { + $source = '../extend/addon/' . $repoName . '/' . $file; + $target = realpath(__DIR__ . '/../../addon/') . '/' . $file; + unlink($target); + if (!symlink($source, $target)) { + logger('Error linking addons to /addon'); + json_return_and_die(array('message' => 'Error linking addons to /addon', 'success' => false)); + } + } + } + $git = new GitRepo('sys', $repoURL, false, $repoName, $repoDir); + $repo = $git->probeRepo(); + json_return_and_die(array('repo' => $repo, 'message' => '', 'success' => true)); + } + case 'addrepo': + require_once('library/markdown.php'); + if (array_key_exists('repoURL', $_REQUEST)) { + require __DIR__ . '/../../library/PHPGit.autoload.php'; // Load PHPGit dependencies + $repoURL = $_REQUEST['repoURL']; + $extendDir = __DIR__ . '/../../store/[data]/git/sys/extend'; + $addonDir = $extendDir . '/addon'; + $tempAddonDir = __DIR__ . '/../../store/[data]/git/sys/temp'; + if (!file_exists($extendDir)) { + if (!mkdir($extendDir, 0770, true)) { + logger('Error creating extend folder: ' . $extendDir); + json_return_and_die(array('message' => 'Error creating extend folder: ' . $extendDir, 'success' => false)); + } else { + if (!symlink(__DIR__ . '/../../extend/addon', $addonDir)) { + logger('Error creating symlink to addon folder: ' . $addonDir); + json_return_and_die(array('message' => 'Error creating symlink to addon folder: ' . $addonDir, 'success' => false)); + } + } + } + if (!is_dir($tempAddonDir)) { + if (!mkdir($tempAddonDir, 0770, true)) { + logger('Error creating temp plugin repo folder: ' . $tempAddonDir); + json_return_and_die(array('message' => 'Error creating temp plugin repo folder: ' . $tempAddonDir, 'success' => false)); + } + } + $repoName = null; + if (array_key_exists('repoName', $_REQUEST) && $_REQUEST['repoName'] !== '') { + $repoName = $_REQUEST['repoName']; + } else { + $repoName = GitRepo::getRepoNameFromURL($repoURL); + } + if (!$repoName) { + logger('Invalid git repo'); + json_return_and_die(array('message' => 'Invalid git repo: ' . $repoName, 'success' => false)); + } + $repoDir = $tempAddonDir . '/' . $repoName; + if (!is_writable($tempAddonDir)) { + logger('Temporary directory for new addon repo is not writable to web server: ' . $tempAddonDir); + json_return_and_die(array('message' => 'Temporary directory for new addon repo is not writable to web server.', 'success' => false)); + } + // clone the repo if new automatically + $git = new GitRepo('sys', $repoURL, true, $repoName, $repoDir); + + $remotes = $git->git->remote(); + $fetchURL = $remotes['origin']['fetch']; + if ($fetchURL !== $git->url) { + if (rrmdir($repoDir)) { + $git = new GitRepo('sys', $repoURL, true, $repoName, $repoDir); + } else { + json_return_and_die(array('message' => 'Error deleting existing addon repo.', 'success' => false)); + } + } + $repo = $git->probeRepo(); + $repo['readme'] = $repo['manifest'] = null; + foreach ($git->git->tree('master') as $object) { + if ($object['type'] == 'blob' && (strtolower($object['file']) === 'readme.md' || strtolower($object['file']) === 'readme')) { + $repo['readme'] = Markdown($git->git->cat->blob($object['hash'])); + } else if ($object['type'] == 'blob' && strtolower($object['file']) === 'manifest.json') { + $repo['manifest'] = $git->git->cat->blob($object['hash']); + } + } + json_return_and_die(array('repo' => $repo, 'message' => '', 'success' => true)); + } else { + json_return_and_die(array('message' => 'No repo URL provided', 'success' => false)); + } + break; + default: + break; + } + } + + function admin_page_profs_post(&$a) { + + if(array_key_exists('basic',$_REQUEST)) { + $arr = explode(',',$_REQUEST['basic']); + for($x = 0; $x < count($arr); $x ++) + if(trim($arr[$x])) + $arr[$x] = trim($arr[$x]); + set_config('system','profile_fields_basic',$arr); + + if(array_key_exists('advanced',$_REQUEST)) { + $arr = explode(',',$_REQUEST['advanced']); + for($x = 0; $x < count($arr); $x ++) + if(trim($arr[$x])) + $arr[$x] = trim($arr[$x]); + set_config('system','profile_fields_advanced',$arr); + } + goaway(z_root() . '/admin/profs'); + } + + + if(array_key_exists('field_name',$_REQUEST)) { + if($_REQUEST['id']) { + $r = q("update profdef set field_name = '%s', field_type = '%s', field_desc = '%s' field_help = '%s', field_inputs = '%s' where id = %d", + dbesc($_REQUEST['field_name']), + dbesc($_REQUEST['field_type']), + dbesc($_REQUEST['field_desc']), + dbesc($_REQUEST['field_help']), + dbesc($_REQUEST['field_inputs']), + intval($_REQUEST['id']) + ); + } + else { + $r = q("insert into profdef ( field_name, field_type, field_desc, field_help, field_inputs ) values ( '%s' , '%s', '%s', '%s', '%s' )", + dbesc($_REQUEST['field_name']), + dbesc($_REQUEST['field_type']), + dbesc($_REQUEST['field_desc']), + dbesc($_REQUEST['field_help']), + dbesc($_REQUEST['field_inputs']) + ); + } + } + + + // add to chosen array basic or advanced + + goaway(z_root() . '/admin/profs'); + } + + function admin_page_profs(&$a) { + + if((argc() > 3) && argv(2) == 'drop' && intval(argv(3))) { + $r = q("delete from profdef where id = %d", + intval(argv(3)) + ); + // remove from allowed fields + + goaway(z_root() . '/admin/profs'); + } + + if((argc() > 2) && argv(2) === 'new') { + return replace_macros(get_markup_template('profdef_edit.tpl'),array( + '$header' => t('New Profile Field'), + '$field_name' => array('field_name',t('Field nickname'),$_REQUEST['field_name'],t('System name of field')), + '$field_type' => array('field_type',t('Input type'),(($_REQUEST['field_type']) ? $_REQUEST['field_type'] : 'text'),''), + '$field_desc' => array('field_desc',t('Field Name'),$_REQUEST['field_desc'],t('Label on profile pages')), + '$field_help' => array('field_help',t('Help text'),$_REQUEST['field_help'],t('Additional info (optional)')), + '$submit' => t('Save') + )); + } + + if((argc() > 2) && intval(argv(2))) { + $r = q("select * from profdef where id = %d limit 1", + intval(argv(2)) + ); + if(! $r) { + notice( t('Field definition not found') . EOL); + goaway(z_root() . '/admin/profs'); + } + + return replace_macros(get_markup_template('profdef_edit.tpl'),array( + '$id' => intval($r[0]['id']), + '$header' => t('Edit Profile Field'), + '$field_name' => array('field_name',t('Field nickname'),$r[0]['field_name'],t('System name of field')), + '$field_type' => array('field_type',t('Input type'),$r[0]['field_type'],''), + '$field_desc' => array('field_desc',t('Field Name'),$r[0]['field_desc'],t('Label on profile pages')), + '$field_help' => array('field_help',t('Help text'),$r[0]['field_help'],t('Additional info (optional)')), + '$submit' => t('Save') + )); + } + + $basic = ''; + $barr = array(); + $fields = get_profile_fields_basic(); + if(! $fields) + $fields = get_profile_fields_basic(1); + if($fields) { + foreach($fields as $k => $v) { + if($basic) + $basic .= ', '; + $basic .= trim($k); + $barr[] = trim($k); + } + } + + $advanced = ''; + $fields = get_profile_fields_advanced(); + if(! $fields) + $fields = get_profile_fields_advanced(1); + if($fields) { + foreach($fields as $k => $v) { + if(in_array(trim($k),$barr)) + continue; + if($advanced) + $advanced .= ', '; + $advanced .= trim($k); + } + } + + $all = ''; + $fields = get_profile_fields_advanced(1); + if($fields) { + foreach($fields as $k => $v) { + if($all) + $all .= ', '; + $all .= trim($k); + } + } + + $r = q("select * from profdef where true"); + if($r) { + foreach($r as $rr) { + if($all) + $all .= ', '; + $all .= $rr['field_name']; + } + } + + + $o = replace_macros(get_markup_template('admin_profiles.tpl'),array( + '$title' => t('Profile Fields'), + '$basic' => array('basic',t('Basic Profile Fields'),$basic,''), + '$advanced' => array('advanced',t('Advanced Profile Fields'),$advanced,t('(In addition to basic fields)')), + '$all' => $all, + '$all_desc' => t('All available fields'), + '$cust_field_desc' => t('Custom Fields'), + '$cust_fields' => $r, + '$edit' => t('Edit'), + '$drop' => t('Delete'), + '$new' => t('Create Custom Field'), + '$submit' => t('Submit') + )); + + return $o; + + + } + +} diff --git a/Zotlabs/Module/Api.php b/Zotlabs/Module/Api.php new file mode 100644 index 000000000..e4744c29f --- /dev/null +++ b/Zotlabs/Module/Api.php @@ -0,0 +1,122 @@ +"; var_dump($e); killme(); + } + + + if(x($_POST,'oauth_yes')){ + + $app = $this->oauth_get_client($request); + if (is_null($app)) return "Invalid request. Unknown token."; + $consumer = new OAuth1Consumer($app['client_id'], $app['pw'], $app['redirect_uri']); + + $verifier = md5($app['secret'].local_channel()); + set_config("oauth", $verifier, local_channel()); + + + if($consumer->callback_url!=null) { + $params = $request->get_parameters(); + $glue="?"; + if (strstr($consumer->callback_url,$glue)) $glue="?"; + goaway($consumer->callback_url . $glue . "oauth_token=" . OAuth1Util::urlencode_rfc3986($params['oauth_token']) . "&oauth_verifier=" . OAuth1Util::urlencode_rfc3986($verifier)); + killme(); + } + + $tpl = get_markup_template("oauth_authorize_done.tpl"); + $o = replace_macros($tpl, array( + '$title' => t('Authorize application connection'), + '$info' => t('Return to your app and insert this Securty Code:'), + '$code' => $verifier, + )); + + return $o; + } + + + if(! local_channel()) { + //TODO: we need login form to redirect to this page + notice( t('Please login to continue.') . EOL ); + return login(false,'api-login',$request->get_parameters()); + } + //FKOAuth1::loginUser(4); + + $app = $this->oauth_get_client($request); + if (is_null($app)) return "Invalid request. Unknown token."; + + + + + $tpl = get_markup_template('oauth_authorize.tpl'); + $o = replace_macros($tpl, array( + '$title' => t('Authorize application connection'), + '$app' => $app, + '$authorize' => t('Do you want to authorize this application to access your posts and contacts, and/or create new posts for you?'), + '$yes' => t('Yes'), + '$no' => t('No'), + )); + + //echo "
"; var_dump($app); killme();
+			
+			return $o;
+		}
+		
+		echo api_call($a);
+		killme();
+	}
+
+	function oauth_get_client($request){
+
+	
+		$params = $request->get_parameters();
+		$token = $params['oauth_token'];
+	
+		$r = q("SELECT `clients`.* 
+			FROM `clients`, `tokens` 
+			WHERE `clients`.`client_id`=`tokens`.`client_id` 
+			AND `tokens`.`id`='%s' AND `tokens`.`auth_scope`='request'",
+			dbesc($token));
+
+		if (!count($r))
+			return null;
+	
+		return $r[0];
+	}
+	
+	
+	
+	
+}
diff --git a/Zotlabs/Module/Appman.php b/Zotlabs/Module/Appman.php
new file mode 100644
index 000000000..a200e986a
--- /dev/null
+++ b/Zotlabs/Module/Appman.php
@@ -0,0 +1,131 @@
+ intval($_REQUEST['uid']),
+				'url' => escape_tags($_REQUEST['url']),
+				'guid' => escape_tags($_REQUEST['guid']),
+				'author' => escape_tags($_REQUEST['author']),
+				'addr' => escape_tags($_REQUEST['addr']),
+				'name' => escape_tags($_REQUEST['name']),
+				'desc' => escape_tags($_REQUEST['desc']),
+				'photo' => escape_tags($_REQUEST['photo']),
+				'version' => escape_tags($_REQUEST['version']),
+				'price' => escape_tags($_REQUEST['price']),
+				'requires' => escape_tags($_REQUEST['requires']),
+				'system' => intval($_REQUEST['system']),
+				'sig' => escape_tags($_REQUEST['sig']),
+				'categories' => escape_tags($_REQUEST['categories'])
+			);
+	
+			$_REQUEST['appid'] = Zlib\Apps::app_install(local_channel(),$arr);
+	
+			if(Zlib\Apps::app_installed(local_channel(),$arr))
+				info( t('App installed.') . EOL);
+	
+			return;
+		}
+	
+	
+		$papp = Zlib\Apps::app_decode($_POST['papp']);
+	
+		if(! is_array($papp)) {
+			notice( t('Malformed app.') . EOL);
+			return;
+		}
+	
+		if($_POST['install']) {
+			Zlib\Apps::app_install(local_channel(),$papp);
+			if(Zlib\Apps::app_installed(local_channel(),$papp))
+				info( t('App installed.') . EOL);
+		}
+	
+		if($_POST['delete']) {
+			Zlib\Apps::app_destroy(local_channel(),$papp);
+		}
+	
+		if($_POST['edit']) {
+			return;
+		}
+	
+		if($_SESSION['return_url']) 
+			goaway(z_root() . '/' . $_SESSION['return_url']);
+		goaway(z_root() . '/apps');
+	
+	
+	}
+	
+	
+	function get() {
+	
+		if(! local_channel()) {
+			notice( t('Permission denied.') . EOL);
+			return;
+		}
+	
+		$channel = \App::get_channel();
+		$app = null;
+		$embed = null;
+		if($_REQUEST['appid']) {
+			$r = q("select * from app where app_id = '%s' and app_channel = %d limit 1",
+				dbesc($_REQUEST['appid']),
+				dbesc(local_channel())
+			);
+			if($r) {
+				$app = $r[0];
+
+				$term = q("select * from term where otype = %d and oid = %d",
+					intval(TERM_OBJ_APP),
+					intval($r[0]['id'])
+				);
+				if($term) {
+					$app['categories'] = '';
+					foreach($term as $t) {
+						if($app['categories'])
+							$app['categories'] .= ',';
+						$app['categories'] .= $t['term'];
+					}
+				}
+			}
+
+			$embed = array('embed', t('Embed code'), Zlib\Apps::app_encode($app,true),'', 'onclick="this.select();"');
+	
+		}
+				
+		return replace_macros(get_markup_template('app_create.tpl'), array(
+	
+			'$banner' => (($app) ? t('Edit App') : t('Create App')),
+			'$app' => $app,
+			'$guid' => (($app) ? $app['app_id'] : ''),
+			'$author' => (($app) ? $app['app_author'] : $channel['channel_hash']),
+			'$addr' => (($app) ? $app['app_addr'] : $channel['xchan_addr']),
+			'$name' => array('name', t('Name of app'),(($app) ? $app['app_name'] : ''), t('Required')),
+			'$url' => array('url', t('Location (URL) of app'),(($app) ? $app['app_url'] : ''), t('Required')),
+	 		'$desc' => array('desc', t('Description'),(($app) ? $app['app_desc'] : ''), ''),
+			'$photo' => array('photo', t('Photo icon URL'),(($app) ? $app['app_photo'] : ''), t('80 x 80 pixels - optional')),
+			'$categories' => array('categories',t('Categories (optional, comma separated list)'),(($app) ? $app['categories'] : ''),''),
+			'$version' => array('version', t('Version ID'),(($app) ? $app['app_version'] : ''), ''),
+			'$price' => array('price', t('Price of app'),(($app) ? $app['app_price'] : ''), ''),
+			'$page' => array('page', t('Location (URL) to purchase app'),(($app) ? $app['app_page'] : ''), ''),
+			'$system' => (($app) ? intval($app['app_system']) : 0),
+			'$requires' => (($app) ? $app['app_requires'] : ''),
+			'$embed' => $embed,
+			'$submit' => t('Submit')
+		));
+	
+	}
+	
+}
diff --git a/Zotlabs/Module/Apps.php b/Zotlabs/Module/Apps.php
new file mode 100644
index 000000000..4bdec4573
--- /dev/null
+++ b/Zotlabs/Module/Apps.php
@@ -0,0 +1,53 @@
+ get_config('system','sitename'),
+			'$cat' => ((array_key_exists('cat',$_GET) && $_GET['cat']) ? ' - ' . escape_tags($_GET['cat']) : ''),
+			'$title' => t('Apps'),
+			'$apps' => $apps,
+		));
+	
+	}
+	
+}
diff --git a/Zotlabs/Module/Attach.php b/Zotlabs/Module/Attach.php
new file mode 100644
index 000000000..de941d52c
--- /dev/null
+++ b/Zotlabs/Module/Attach.php
@@ -0,0 +1,61 @@
+ 2) ? intval(argv(2)) : 0));
+	
+		if(! $r['success']) {
+			notice( $r['message'] . EOL);
+			return;
+		}
+	
+		$c = q("select channel_address from channel where channel_id = %d limit 1",
+			intval($r['data']['uid'])
+		);
+	
+		if(! $c)
+			return;
+	
+	
+		$unsafe_types = array('text/html','text/css','application/javascript');
+	
+		if(in_array($r['data']['filetype'],$unsafe_types)) {
+				header('Content-type: text/plain');
+		}
+		else {
+			header('Content-type: ' . $r['data']['filetype']);
+		}
+	
+		header('Content-disposition: attachment; filename="' . $r['data']['filename'] . '"');
+		if(intval($r['data']['os_storage'])) {
+			$fname = dbunescbin($r['data']['content']);
+			if(strpos($fname,'store') !== false)
+				$istream = fopen($fname,'rb');
+			else
+				$istream = fopen('store/' . $c[0]['channel_address'] . '/' . $fname,'rb');
+			$ostream = fopen('php://output','wb');
+			if($istream && $ostream) {
+				pipe_streams($istream,$ostream);
+				fclose($istream);
+				fclose($ostream);
+			}
+		}
+		else
+			echo dbunescbin($r['data']['content']);
+		killme();
+	
+	}
+	
+}
diff --git a/Zotlabs/Module/Authtest.php b/Zotlabs/Module/Authtest.php
new file mode 100644
index 000000000..239ae3bdb
--- /dev/null
+++ b/Zotlabs/Module/Authtest.php
@@ -0,0 +1,61 @@
+Magic-Auth Diagnostic';
+	
+		if(! local_channel()) {
+			notice( t('Permission denied.') . EOL);
+			return $o;
+		}
+	
+		$o .= '
'; + $o .= 'Target URL: '; + $o .= '
'; + + $o .= '

'; + + if(x($_GET,'dest')) { + if(strpos($_GET['dest'],'@')) { + $_GET['dest'] = $_REQUEST['dest'] = 'https://' . substr($_GET['dest'],strpos($_GET['dest'],'@')+1) . '/channel/' . substr($_GET['dest'],0,strpos($_GET['dest'],'@')); + } + + $_REQUEST['test'] = 1; + $mod = new Magic(); + $x = $mod->init($a); + + $o .= 'Local Setup returns: ' . print_r($x,true); + + + + if($x['url']) { + $z = z_fetch_url($x['url'] . '&test=1'); + if($z['success']) { + $j = json_decode($z['body'],true); + if(! $j) + $o .= 'json_decode failure from remote site. ' . print_r($z['body'],true); + $o .= 'Remote site responded: ' . print_r($j,true); + if($j['success'] && strpos($j['message'],'Authentication Success')) + $auth_success = true; + } + else { + $o .= 'fetch url failure.' . print_r($z,true); + } + } + + if(! $auth_success) + $o .= 'Authentication Failed!' . EOL; + } + + return str_replace("\n",'
',$o); + } + +} diff --git a/Zotlabs/Module/Block.php b/Zotlabs/Module/Block.php new file mode 100644 index 000000000..e671730f6 --- /dev/null +++ b/Zotlabs/Module/Block.php @@ -0,0 +1,92 @@ + 1 && argv(1) === 'sys' && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + \App::$is_sys = true; + } + } + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + + function get() { + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $which = argv(1); + + $_SESSION['return_url'] = \App::$query_string; + + $uid = local_channel(); + $owner = 0; + $channel = null; + $observer = \App::get_observer(); + + $channel = \App::get_channel(); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + $uid = $owner = intval($sys['channel_id']); + $channel = $sys; + $observer = $sys; + } + } + + if(! $owner) { + // Figure out who the page owner is. + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms($owner,$ob_hash); + + if(! $perms['write_pages']) { + notice( t('Permission denied.') . EOL); + return; + } + + // Block design features from visitors + + if((! $uid) || ($uid != $owner)) { + notice( t('Permission denied.') . EOL); + return; + } + + $mimetype = (($_REQUEST['mimetype']) ? $_REQUEST['mimetype'] : get_pconfig($owner,'system','page_mimetype')); + + $x = array( + 'webpage' => ITEM_TYPE_BLOCK, + 'is_owner' => true, + 'nickname' => \App::$profile['channel_address'], + 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), + 'bang' => '', + 'showacl' => false, + 'visitor' => true, + 'mimetype' => $mimetype, + 'mimeselect' => true, + 'hide_location' => true, + 'ptlabel' => t('Block Name'), + 'profile_uid' => intval($owner), + 'expanded' => true, + 'novoting' => true, + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ); + + if($_REQUEST['title']) + $x['title'] = $_REQUEST['title']; + if($_REQUEST['body']) + $x['body'] = $_REQUEST['body']; + if($_REQUEST['pagetitle']) + $x['pagetitle'] = $_REQUEST['pagetitle']; + + $editor = status_editor($a,$x); + + + $r = q("select iconfig.iid, iconfig.k, iconfig.v, mid, title, body, mimetype, created, edited from iconfig + left join item on iconfig.iid = item.id + where uid = %d and iconfig.cat = 'system' and iconfig.k = 'BUILDBLOCK' + and item_type = %d order by item.created desc", + intval($owner), + intval(ITEM_TYPE_BLOCK) + ); + + $pages = null; + + if($r) { + $pages = array(); + foreach($r as $rr) { + $element_arr = array( + 'type' => 'block', + 'title' => $rr['title'], + 'body' => $rr['body'], + 'created' => $rr['created'], + 'edited' => $rr['edited'], + 'mimetype' => $rr['mimetype'], + 'pagetitle' => $rr['v'], + 'mid' => $rr['mid'] + ); + $pages[$rr['iid']][] = array( + 'url' => $rr['iid'], + 'name' => $rr['v'], + 'title' => $rr['title'], + 'created' => $rr['created'], + 'edited' => $rr['edited'], + 'bb_element' => '[element]' . base64url_encode(json_encode($element_arr)) . '[/element]' + ); + } + } + + //Build the base URL for edit links + $url = z_root() . '/editblock/' . $which; + + $o .= replace_macros(get_markup_template('blocklist.tpl'), array( + '$baseurl' => $url, + '$title' => t('Blocks'), + '$name' => t('Block Name'), + '$blocktitle' => t('Block Title'), + '$created' => t('Created'), + '$edited' => t('Edited'), + '$create' => t('Create'), + '$edit' => t('Edit'), + '$share' => t('Share'), + '$delete' => t('Delete'), + '$editor' => $editor, + '$pages' => $pages, + '$channel' => $which, + '$view' => t('View'), + '$preview' => '1', + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Bookmarks.php b/Zotlabs/Module/Bookmarks.php new file mode 100644 index 000000000..733bfd4e3 --- /dev/null +++ b/Zotlabs/Module/Bookmarks.php @@ -0,0 +1,105 @@ +'; + + $o .= '

' . t('My Bookmarks') . '

'; + + $x = menu_list(local_channel(),'',MENU_BOOKMARK); + + if($x) { + foreach($x as $xx) { + $y = menu_fetch($xx['menu_name'],local_channel(),get_observer_hash()); + $o .= menu_render($y,'',true); + } + } + + $o .= '

' . t('My Connections Bookmarks') . '

'; + + + $x = menu_list(local_channel(),'',MENU_SYSTEM|MENU_BOOKMARK); + + if($x) { + foreach($x as $xx) { + $y = menu_fetch($xx['menu_name'],local_channel(),get_observer_hash()); + $o .= menu_render($y,'',true); + } + } + + $o .= ''; + + return $o; + + } + + +} diff --git a/Zotlabs/Module/Branchtopic.php b/Zotlabs/Module/Branchtopic.php new file mode 100644 index 000000000..87a1a43e9 --- /dev/null +++ b/Zotlabs/Module/Branchtopic.php @@ -0,0 +1,47 @@ + 1) + $item_id = intval(argv(1)); + + if(! $item_id) + return; + + $channel = \App::get_channel(); + + if(! $channel) + return; + + + $r = q("select * from item where id = %d and uid = %d and owner_xchan = '%s' and id != parent limit 1", + intval($item_id), + intval(local_channel()), + dbesc($channel['channel_hash']) + ); + + if(! $r) + return; + + $p = q("select * from item where id = %d and uid = %d limit 1", + intval($r[0]['parent']), + intval(local_channel()) + ); + + $x = q("update item set parent = id, route = '', item_thread_top = 1 where id = %d", + intval($item_id) + ); + + return; + } + +} diff --git a/Zotlabs/Module/Cal.php b/Zotlabs/Module/Cal.php new file mode 100644 index 000000000..fd4169e68 --- /dev/null +++ b/Zotlabs/Module/Cal.php @@ -0,0 +1,354 @@ + 1) { + $nick = argv(1); + + profile_load($nick); + + $channelx = channelx_by_nick($nick); + + if(! $channelx) + return; + + \App::$data['channel'] = $channelx; + + $observer = \App::get_observer(); + \App::$data['observer'] = $observer; + + $observer_xchan = (($observer) ? $observer['xchan_hash'] : ''); + + head_set_icon(\App::$data['channel']['xchan_photo_s']); + + \App::$page['htmlhead'] .= "" ; + + } + + return; + } + + + + function get() { + + if(observer_prohibited()) { + return; + } + + $channel = null; + + if(argc() > 1) { + $channel = channelx_by_nick(argv(1)); + } + + + if(! $channel) { + notice( t('Channel not found.') . EOL); + return; + } + + // since we don't currently have an event permission - use the stream permission + + if(! perm_is_allowed($channel['channel_id'], get_observer_hash(), 'view_stream')) { + notice( t('Permissions denied.') . EOL); + return; + } + + $sql_extra = permissions_sql($channel['channel_id'],get_observer_hash(),'event'); + + $first_day = get_pconfig(local_channel(),'system','cal_first_day'); + $first_day = (($first_day) ? $first_day : 0); + + $htpl = get_markup_template('event_head.tpl'); + \App::$page['htmlhead'] .= replace_macros($htpl,array( + '$baseurl' => z_root(), + '$module_url' => '/cal/' . $channel['channel_address'], + '$modparams' => 2, + '$lang' => \App::$language, + '$first_day' => $first_day + )); + + $o = ''; + + $tabs = profile_tabs($a, True, $channel['channel_address']); + + $mode = 'view'; + $y = 0; + $m = 0; + $ignored = ((x($_REQUEST,'ignored')) ? " and dismissed = " . intval($_REQUEST['ignored']) . " " : ''); + + // logger('args: ' . print_r(\App::$argv,true)); + + if(argc() > 3 && intval(argv(2)) && intval(argv(3))) { + $mode = 'view'; + $y = intval(argv(2)); + $m = intval(argv(3)); + } + if(argc() <= 3) { + $mode = 'view'; + $event_id = argv(2); + } + + if($mode == 'view') { + + /* edit/create form */ + if($event_id) { + $r = q("SELECT * FROM `event` WHERE event_hash = '%s' AND `uid` = %d LIMIT 1", + dbesc($event_id), + intval($channel['channel_id']) + ); + if(count($r)) + $orig_event = $r[0]; + } + + + // Passed parameters overrides anything found in the DB + if(!x($orig_event)) + $orig_event = array(); + + + + $tz = date_default_timezone_get(); + if(x($orig_event)) + $tz = (($orig_event['adjust']) ? date_default_timezone_get() : 'UTC'); + + $syear = datetime_convert('UTC', $tz, $sdt, 'Y'); + $smonth = datetime_convert('UTC', $tz, $sdt, 'm'); + $sday = datetime_convert('UTC', $tz, $sdt, 'd'); + $shour = datetime_convert('UTC', $tz, $sdt, 'H'); + $sminute = datetime_convert('UTC', $tz, $sdt, 'i'); + + $stext = datetime_convert('UTC',$tz,$sdt); + $stext = substr($stext,0,14) . "00:00"; + + $fyear = datetime_convert('UTC', $tz, $fdt, 'Y'); + $fmonth = datetime_convert('UTC', $tz, $fdt, 'm'); + $fday = datetime_convert('UTC', $tz, $fdt, 'd'); + $fhour = datetime_convert('UTC', $tz, $fdt, 'H'); + $fminute = datetime_convert('UTC', $tz, $fdt, 'i'); + + $ftext = datetime_convert('UTC',$tz,$fdt); + $ftext = substr($ftext,0,14) . "00:00"; + + $type = ((x($orig_event)) ? $orig_event['etype'] : 'event'); + + $f = get_config('system','event_input_format'); + if(! $f) + $f = 'ymd'; + + $catsenabled = feature_enabled($channel['channel_id'],'categories'); + + + $show_bd = perm_is_allowed($channel['channel_id'], get_observer_hash(), 'view_contacts'); + if(! $show_bd) { + $sql_extra .= " and event.etype != 'birthday' "; + } + + + $category = ''; + + $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); + $thismonth = datetime_convert('UTC',date_default_timezone_get(),'now','m'); + if(! $y) + $y = intval($thisyear); + if(! $m) + $m = intval($thismonth); + + // Put some limits on dates. The PHP date functions don't seem to do so well before 1900. + // An upper limit was chosen to keep search engines from exploring links millions of years in the future. + + if($y < 1901) + $y = 1900; + if($y > 2099) + $y = 2100; + + $nextyear = $y; + $nextmonth = $m + 1; + if($nextmonth > 12) { + $nextmonth = 1; + $nextyear ++; + } + + $prevyear = $y; + if($m > 1) + $prevmonth = $m - 1; + else { + $prevmonth = 12; + $prevyear --; + } + + $dim = get_dim($y,$m); + $start = sprintf('%d-%d-%d %d:%d:%d',$y,$m,1,0,0,0); + $finish = sprintf('%d-%d-%d %d:%d:%d',$y,$m,$dim,23,59,59); + + + if (argv(2) === 'json'){ + if (x($_GET,'start')) $start = $_GET['start']; + if (x($_GET,'end')) $finish = $_GET['end']; + } + + $start = datetime_convert('UTC','UTC',$start); + $finish = datetime_convert('UTC','UTC',$finish); + + $adjust_start = datetime_convert('UTC', date_default_timezone_get(), $start); + $adjust_finish = datetime_convert('UTC', date_default_timezone_get(), $finish); + + if (x($_GET,'id')){ + $r = q("SELECT event.*, item.plink, item.item_flags, item.author_xchan, item.owner_xchan + from event left join item on resource_id = event_hash where resource_type = 'event' and event.uid = %d and event.id = %d $sql_extra limit 1", + intval($channel['channel_id']), + intval($_GET['id']) + ); + } + else { + // fixed an issue with "nofinish" events not showing up in the calendar. + // There's still an issue if the finish date crosses the end of month. + // Noting this for now - it will need to be fixed here and in Friendica. + // Ultimately the finish date shouldn't be involved in the query. + + $r = q("SELECT event.*, item.plink, item.item_flags, item.author_xchan, item.owner_xchan + from event left join item on event_hash = resource_id + where resource_type = 'event' and event.uid = %d $ignored + AND (( adjust = 0 AND ( dtend >= '%s' or nofinish = 1 ) AND dtstart <= '%s' ) + OR ( adjust = 1 AND ( dtend >= '%s' or nofinish = 1 ) AND dtstart <= '%s' )) $sql_extra ", + intval($channel['channel_id']), + dbesc($start), + dbesc($finish), + dbesc($adjust_start), + dbesc($adjust_finish) + ); + + } + + $links = array(); + + if($r) { + xchan_query($r); + $r = fetch_post_tags($r,true); + + $r = sort_by_date($r); + } + + if($r) { + foreach($r as $rr) { + $j = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'j') : datetime_convert('UTC','UTC',$rr['dtstart'],'j')); + if(! x($links,$j)) + $links[$j] = z_root() . '/' . \App::$cmd . '#link-' . $j; + } + } + + $events=array(); + + $last_date = ''; + $fmt = t('l, F j'); + + if($r) { + + foreach($r as $rr) { + + $j = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'j') : datetime_convert('UTC','UTC',$rr['dtstart'],'j')); + $d = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], $fmt) : datetime_convert('UTC','UTC',$rr['dtstart'],$fmt)); + $d = day_translate($d); + + $start = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'c') : datetime_convert('UTC','UTC',$rr['dtstart'],'c')); + if ($rr['nofinish']){ + $end = null; + } else { + $end = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtend'], 'c') : datetime_convert('UTC','UTC',$rr['dtend'],'c')); + } + + + $is_first = ($d !== $last_date); + + $last_date = $d; + + $edit = false; + + $drop = false; + + $title = strip_tags(html_entity_decode(bbcode($rr['summary']),ENT_QUOTES,'UTF-8')); + if(! $title) { + list($title, $_trash) = explode("$rr['id'], + 'hash' => $rr['event_hash'], + 'start'=> $start, + 'end' => $end, + 'drop' => $drop, + 'allDay' => false, + 'title' => $title, + + 'j' => $j, + 'd' => $d, + 'edit' => $edit, + 'is_first'=>$is_first, + 'item'=>$rr, + 'html'=>$html, + 'plink' => array($rr['plink'],t('Link to Source'),'',''), + ); + + + } + } + + if (argv(2) === 'json'){ + echo json_encode($events); killme(); + } + + // links: array('href', 'text', 'extra css classes', 'title') + if (x($_GET,'id')){ + $tpl = get_markup_template("event_cal.tpl"); + } + else { + $tpl = get_markup_template("events_cal-js.tpl"); + } + + $nick = $channel['channel_address']; + + $o = replace_macros($tpl, array( + '$baseurl' => z_root(), + '$new_event' => array(z_root().'/cal',(($event_id) ? t('Edit Event') : t('Create Event')),'',''), + '$previus' => array(z_root()."/cal/$nick/$prevyear/$prevmonth",t('Previous'),'',''), + '$next' => array(z_root()."/cal/$nick/$nextyear/$nextmonth",t('Next'),'',''), + '$export' => array(z_root()."/cal/$nick/$y/$m/export",t('Export'),'',''), + '$calendar' => cal($y,$m,$links, ' eventcal'), + '$events' => $events, + '$upload' => t('Import'), + '$submit' => t('Submit'), + '$prev' => t('Previous'), + '$next' => t('Next'), + '$today' => t('Today'), + '$form' => $form, + '$expandform' => ((x($_GET,'expandform')) ? true : false), + '$tabs' => $tabs + )); + + if (x($_GET,'id')){ echo $o; killme(); } + + return $o; + } + + } + +} diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php new file mode 100644 index 000000000..c74802ec5 --- /dev/null +++ b/Zotlabs/Module/Channel.php @@ -0,0 +1,366 @@ + 1) + $which = argv(1); + if(! $which) { + if(local_channel()) { + $channel = \App::get_channel(); + if($channel && $channel['channel_address']) + $which = $channel['channel_address']; + } + } + if(! $which) { + notice( t('You must be logged in to see this page.') . EOL ); + return; + } + + $profile = 0; + $channel = \App::get_channel(); + + if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) { + $which = $channel['channel_address']; + $profile = argv(1); + } + + \App::$page['htmlhead'] .= '' . "\r\n" ; + \App::$page['htmlhead'] .= '' . "\r\n" ; + + // Not yet ready for prime time + // \App::$page['htmlhead'] .= '' . "\r\n" ; + // \App::$page['htmlhead'] .= '' . "\r\n" ; + + // Run profile_load() here to make sure the theme is set before + // we start loading content + + profile_load($which,$profile); + + } + + function get($update = 0, $load = false) { + + + if($load) + $_SESSION['loadtime'] = datetime_convert(); + + $checkjs = new \Zotlabs\Web\CheckJS(1); + + $category = $datequery = $datequery2 = ''; + + $mid = ((x($_REQUEST,'mid')) ? $_REQUEST['mid'] : ''); + + $datequery = ((x($_GET,'dend') && is_a_date_arg($_GET['dend'])) ? notags($_GET['dend']) : ''); + $datequery2 = ((x($_GET,'dbegin') && is_a_date_arg($_GET['dbegin'])) ? notags($_GET['dbegin']) : ''); + + if(observer_prohibited(true)) { + return login(); + } + + $category = ((x($_REQUEST,'cat')) ? $_REQUEST['cat'] : ''); + $hashtags = ((x($_REQUEST,'tag')) ? $_REQUEST['tag'] : ''); + + $groups = array(); + + $o = ''; + + if($update) { + // Ensure we've got a profile owner if updating. + \App::$profile['profile_uid'] = \App::$profile_uid = $update; + } + else { + if(\App::$profile['profile_uid'] == local_channel()) { + nav_set_selected('home'); + } + } + + $is_owner = (((local_channel()) && (\App::$profile['profile_uid'] == local_channel())) ? true : false); + + $channel = \App::get_channel(); + $observer = \App::get_observer(); + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms(\App::$profile['profile_uid'],$ob_hash); + + if(! $perms['view_stream']) { + // We may want to make the target of this redirect configurable + if($perms['view_profile']) { + notice( t('Insufficient permissions. Request redirected to profile page.') . EOL); + goaway (z_root() . "/profile/" . \App::$profile['channel_address']); + } + notice( t('Permission denied.') . EOL); + return; + } + + + if(! $update) { + + $o .= profile_tabs($a, $is_owner, \App::$profile['channel_address']); + + $o .= common_friends_visitor_widget(\App::$profile['profile_uid']); + + if($channel && $is_owner) { + $channel_acl = array( + 'allow_cid' => $channel['channel_allow_cid'], + 'allow_gid' => $channel['channel_allow_gid'], + 'deny_cid' => $channel['channel_deny_cid'], + 'deny_gid' => $channel['channel_deny_gid'] + ); + } + else + $channel_acl = array(); + + + if($perms['post_wall']) { + + $x = array( + 'is_owner' => $is_owner, + 'allow_location' => ((($is_owner || $observer) && (intval(get_pconfig(\App::$profile['profile_uid'],'system','use_browser_location')))) ? true : false), + 'default_location' => (($is_owner) ? \App::$profile['channel_location'] : ''), + 'nickname' => \App::$profile['channel_address'], + 'lockstate' => (((strlen(\App::$profile['channel_allow_cid'])) || (strlen(\App::$profile['channel_allow_gid'])) || (strlen(\App::$profile['channel_deny_cid'])) || (strlen(\App::$profile['channel_deny_gid']))) ? 'lock' : 'unlock'), + 'acl' => (($is_owner) ? populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post') : ''), + 'showacl' => (($is_owner) ? 'yes' : ''), + 'bang' => '', + 'visitor' => (($is_owner || $observer) ? true : false), + 'profile_uid' => \App::$profile['profile_uid'], + 'editor_autocomplete' => true, + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ); + + $o .= status_editor($a,$x); + } + + } + + + /** + * Get permissions SQL - if $remote_contact is true, our remote user has been pre-verified and we already have fetched his/her groups + */ + + $item_normal = item_normal(); + $sql_extra = item_permissions_sql(\App::$profile['profile_uid']); + + if(get_pconfig(\App::$profile['profile_uid'],'system','channel_list_mode') && (! $mid)) + $page_mode = 'list'; + else + $page_mode = 'client'; + + $abook_uids = " and abook.abook_channel = " . intval(\App::$profile['profile_uid']) . " "; + + $simple_update = (($update) ? " AND item_unseen = 1 " : ''); + + \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + + if($update && $_SESSION['loadtime']) + $simple_update = " AND (( item_unseen = 1 AND item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) OR item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) "; + if($load) + $simple_update = ''; + + if(($update) && (! $load)) { + + if($mid) { + $r = q("SELECT parent AS item_id from item where mid like '%s' and uid = %d $item_normal + AND item_wall = 1 AND item_unseen = 1 $sql_extra limit 1", + dbesc($mid . '%'), + intval(\App::$profile['profile_uid']) + ); + } + else { + $r = q("SELECT distinct parent AS `item_id`, created from item + left join abook on ( item.owner_xchan = abook.abook_xchan $abook_uids ) + WHERE uid = %d $item_normal + AND item_wall = 1 $simple_update + AND (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra + ORDER BY created DESC", + intval(\App::$profile['profile_uid']) + ); + $_SESSION['loadtime'] = datetime_convert(); + } + + } + else { + + if(x($category)) { + $sql_extra .= protect_sprintf(term_query('item', $category, TERM_CATEGORY)); + } + if(x($hashtags)) { + $sql_extra .= protect_sprintf(term_query('item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG)); + } + + if($datequery) { + $sql_extra2 .= protect_sprintf(sprintf(" AND item.created <= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$datequery)))); + } + if($datequery2) { + $sql_extra2 .= protect_sprintf(sprintf(" AND item.created >= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$datequery2)))); + } + + $itemspage = get_pconfig(local_channel(),'system','itemspage'); + \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + + if($load || ($checkjs->disabled())) { + if($mid) { + $r = q("SELECT parent AS item_id from item where mid = '%s' and uid = %d $item_normal + AND item_wall = 1 $sql_extra limit 1", + dbesc($mid), + intval(\App::$profile['profile_uid']) + ); + if (! $r) { + notice( t('Permission denied.') . EOL); + } + + } + else { + $r = q("SELECT distinct id AS item_id, created FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE uid = %d $item_normal + AND item_wall = 1 and item_thread_top = 1 + AND (abook_blocked = 0 or abook.abook_flags is null) + $sql_extra $sql_extra2 + ORDER BY created DESC $pager_sql ", + intval(\App::$profile['profile_uid']) + ); + } + } + else { + $r = array(); + } + } + + if($r) { + + $parents_str = ids_to_querystr($r,'item_id'); + + $items = q("SELECT `item`.*, `item`.`id` AS `item_id` + FROM `item` + WHERE `item`.`uid` = %d $item_normal + AND `item`.`parent` IN ( %s ) + $sql_extra ", + intval(\App::$profile['profile_uid']), + dbesc($parents_str) + ); + + xchan_query($items); + $items = fetch_post_tags($items, true); + $items = conv_sort($items,'created'); + + if($load && $mid && (! count($items))) { + // This will happen if we don't have sufficient permissions + // to view the parent item (or the item itself if it is toplevel) + notice( t('Permission denied.') . EOL); + } + + } + else { + $items = array(); + } + + if((! $update) && (! $load)) { + + // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, + // because browser prefetching might change it on us. We have to deliver it with the page. + + $maxheight = get_pconfig(\App::$profile['profile_uid'],'system','channel_divmore_height'); + if(! $maxheight) + $maxheight = 400; + + $o .= '
' . "\r\n"; + $o .= "\r\n"; + + \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + '$baseurl' => z_root(), + '$pgtype' => 'channel', + '$uid' => ((\App::$profile['profile_uid']) ? \App::$profile['profile_uid'] : '0'), + '$gid' => '0', + '$cid' => '0', + '$cmin' => '0', + '$cmax' => '0', + '$star' => '0', + '$liked' => '0', + '$conv' => '0', + '$spam' => '0', + '$nouveau' => '0', + '$wall' => '1', + '$fh' => '0', + '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$search' => '', + '$order' => '', + '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0), + '$file' => '', + '$cats' => (($category) ? $category : ''), + '$tags' => (($hashtags) ? $hashtags : ''), + '$mid' => $mid, + '$verb' => '', + '$dend' => $datequery, + '$dbegin' => $datequery2 + )); + + + } + + $update_unseen = ''; + + if($page_mode === 'list') { + + /** + * in "list mode", only mark the parent item and any like activities as "seen". + * We won't distinguish between comment likes and post likes. The important thing + * is that the number of unseen comments will be accurate. The SQL to separate the + * comment likes could also get somewhat hairy. + */ + + if($parents_str) { + $update_unseen = " AND ( id IN ( " . dbesc($parents_str) . " )"; + $update_unseen .= " OR ( parent IN ( " . dbesc($parents_str) . " ) AND verb in ( '" . dbesc(ACTIVITY_LIKE) . "','" . dbesc(ACTIVITY_DISLIKE) . "' ))) "; + } + } + else { + if($parents_str) { + $update_unseen = " AND parent IN ( " . dbesc($parents_str) . " )"; + } + } + + if($is_owner && $update_unseen) { + $r = q("UPDATE item SET item_unseen = 0 where item_unseen = 1 and item_wall = 1 AND uid = %d $update_unseen", + intval(local_channel()) + ); + } + + + if($checkjs->disabled()) { + $o .= conversation($a,$items,'channel',$update,'traditional'); + } + else { + $o .= conversation($a,$items,'channel',$update,$page_mode); + } + + if((! $update) || ($checkjs->disabled())) { + $o .= alt_pager($a,count($items)); + if ($mid && $items[0]['title']) + \App::$page['title'] = $items[0]['title'] . " - " . \App::$page['title']; + } + + if($mid) + $o .= '
'; + + return $o; + } +} \ No newline at end of file diff --git a/Zotlabs/Module/Chanview.php b/Zotlabs/Module/Chanview.php new file mode 100644 index 000000000..c6dd07eb7 --- /dev/null +++ b/Zotlabs/Module/Chanview.php @@ -0,0 +1,105 @@ + $url, + // '$full' => t('toggle full screen mode') + // )); + + // return $o; + + } + +} diff --git a/Zotlabs/Module/Chat.php b/Zotlabs/Module/Chat.php new file mode 100644 index 000000000..ff55a9319 --- /dev/null +++ b/Zotlabs/Module/Chat.php @@ -0,0 +1,263 @@ + 1) + $which = argv(1); + if(! $which) { + if(local_channel()) { + $channel = \App::get_channel(); + if($channel && $channel['channel_address']) + $which = $channel['channel_address']; + } + } + if(! $which) { + notice( t('You must be logged in to see this page.') . EOL ); + return; + } + + $profile = 0; + $channel = \App::get_channel(); + + if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) { + $which = $channel['channel_address']; + $profile = argv(1); + } + + \App::$page['htmlhead'] .= '' . "\r\n" ; + + // Run profile_load() here to make sure the theme is set before + // we start loading content + + profile_load($which,$profile); + + } + + function post() { + + if($_POST['room_name']) + $room = strip_tags(trim($_POST['room_name'])); + + if((! $room) || (! local_channel())) + return; + + $channel = \App::get_channel(); + + + if($_POST['action'] === 'drop') { + logger('delete chatroom'); + Zlib\Chatroom::destroy($channel,array('cr_name' => $room)); + goaway(z_root() . '/chat/' . $channel['channel_address']); + } + + $acl = new \Zotlabs\Access\AccessList($channel); + $acl->set_from_array($_REQUEST); + + $arr = $acl->get(); + $arr['name'] = $room; + $arr['expire'] = intval($_POST['chat_expire']); + if(intval($arr['expire']) < 0) + $arr['expire'] = 0; + + Zlib\Chatroom::create($channel,$arr); + + $x = q("select * from chatroom where cr_name = '%s' and cr_uid = %d limit 1", + dbesc($room), + intval(local_channel()) + ); + + build_sync_packet(0, array('chatroom' => $x)); + + if($x) + goaway(z_root() . '/chat/' . $channel['channel_address'] . '/' . $x[0]['cr_id']); + + // that failed. Try again perhaps? + + goaway(z_root() . '/chat/' . $channel['channel_address'] . '/new'); + + + } + + + function get() { + + if(local_channel()) + $channel = \App::get_channel(); + + $ob = \App::get_observer(); + $observer = get_observer_hash(); + if(! $observer) { + notice( t('Permission denied.') . EOL); + return; + } + + if(! perm_is_allowed(\App::$profile['profile_uid'],$observer,'chat')) { + notice( t('Permission denied.') . EOL); + return; + } + + if((argc() > 3) && intval(argv(2)) && (argv(3) === 'leave')) { + Zlib\Chatroom::leave($observer,argv(2),$_SERVER['REMOTE_ADDR']); + goaway(z_root() . '/channel/' . argv(1)); + } + + + if((argc() > 3) && intval(argv(2)) && (argv(3) === 'status')) { + $ret = array('success' => false); + $room_id = intval(argv(2)); + if(! $room_id || ! $observer) + return; + + $r = q("select * from chatroom where cr_id = %d limit 1", + intval($room_id) + ); + if(! $r) { + json_return_and_die($ret); + } + 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) { + json_return_and_die($ret); + } + $y = q("select count(*) as total from chatpresence where cp_room = %d", + intval($room_id) + ); + if($y) { + $ret['success'] = true; + $ret['chatroom'] = $r[0]['cr_name']; + $ret['inroom'] = $y[0]['total']; + } + + // figure out how to present a timestamp of the last activity, since we don't know the observer's timezone. + + $z = q("select created from chat where chat_room = %d order by created desc limit 1", + intval($room_id) + ); + if($z) { + $ret['last'] = $z[0]['created']; + } + json_return_and_die($ret); + } + + + if(argc() > 2 && intval(argv(2))) { + + $room_id = intval(argv(2)); + $bookmark_link = get_bookmark_link($ob); + + $x = Zlib\Chatroom::enter($observer,$room_id,'online',$_SERVER['REMOTE_ADDR']); + if(! $x) + return; + $x = q("select * from chatroom where cr_id = %d and cr_uid = %d $sql_extra limit 1", + intval($room_id), + intval(\App::$profile['profile_uid']) + ); + + if($x) { + $acl = new \Zotlabs\Access\AccessList(false); + $acl->set($x[0]); + + $private = $acl->is_private(); + $room_name = $x[0]['cr_name']; + if($bookmark_link) + $bookmark_link .= '&url=' . z_root() . '/chat/' . argv(1) . '/' . argv(2) . '&title=' . urlencode($x[0]['cr_name']) . (($private) ? '&private=1' : '') . '&ischat=1'; + } + else { + notice( t('Room not found') . EOL); + return; + } + + $cipher = get_pconfig(local_channel(),'system','default_cipher'); + if(! $cipher) + $cipher = 'aes256'; + + + $o = replace_macros(get_markup_template('chat.tpl'),array( + '$is_owner' => ((local_channel() && local_channel() == $x[0]['cr_uid']) ? true : false), + '$room_name' => $room_name, + '$room_id' => $room_id, + '$baseurl' => z_root(), + '$nickname' => argv(1), + '$submit' => t('Submit'), + '$leave' => t('Leave Room'), + '$drop' => t('Delete Room'), + '$away' => t('I am away right now'), + '$online' => t('I am online'), + '$bookmark_link' => $bookmark_link, + '$bookmark' => t('Bookmark this room'), + '$feature_encrypt' => ((feature_enabled(local_channel(),'content_encrypt')) ? true : false), + '$cipher' => $cipher, + '$linkurl' => t('Please enter a link URL:'), + '$encrypt' => t('Encrypt text'), + '$insert' => t('Insert web link') + )); + return $o; + } + + + require_once('include/conversation.php'); + + $o = profile_tabs($a,((local_channel() && local_channel() == \App::$profile['profile_uid']) ? true : false),\App::$profile['channel_address']); + + if(! feature_enabled(\App::$profile['profile_uid'],'ajaxchat')) { + notice( t('Feature disabled.') . EOL); + return $o; + } + + + $acl = new \Zotlabs\Access\AccessList($channel); + $channel_acl = $acl->get(); + + $lockstate = (($channel_acl['allow_cid'] || $channel_acl['allow_gid'] || $channel_acl['deny_cid'] || $channel_acl['deny_gid']) ? 'lock' : 'unlock'); + require_once('include/acl_selectors.php'); + + $chatroom_new = ''; + if(local_channel()) { + $chatroom_new = replace_macros(get_markup_template('chatroom_new.tpl'),array( + '$header' => t('New Chatroom'), + '$name' => array('room_name',t('Chatroom name'),'', ''), + '$chat_expire' => array('chat_expire',t('Expiration of chats (minutes)'),120,''), + '$permissions' => t('Permissions'), + '$acl' => populate_acl($channel_acl,false), + '$lockstate' => $lockstate, + '$submit' => t('Submit') + + )); + } + + $rooms = Zlib\Chatroom::roomlist(\App::$profile['profile_uid']); + + $o .= replace_macros(get_markup_template('chatrooms.tpl'), array( + '$header' => sprintf( t('%1$s\'s Chatrooms'), \App::$profile['fullname']), + '$name' => t('Name'), + '$baseurl' => z_root(), + '$nickname' => \App::$profile['channel_address'], + '$rooms' => $rooms, + '$norooms' => t('No chatrooms available'), + '$newroom' => t('Create New'), + '$is_owner' => ((local_channel() && local_channel() == \App::$profile['profile_uid']) ? 1 : 0), + '$chatroom_new' => $chatroom_new, + '$expire' => t('Expiration'), + '$expire_unit' => t('min') //minutes + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Chatsvc.php b/Zotlabs/Module/Chatsvc.php new file mode 100644 index 000000000..6a28a7c4d --- /dev/null +++ b/Zotlabs/Module/Chatsvc.php @@ -0,0 +1,170 @@ + false); + + \App::$data['chat']['room_id'] = intval($_REQUEST['room_id']); + $x = q("select cr_uid from chatroom where cr_id = %d and cr_id != 0 limit 1", + intval(\App::$data['chat']['room_id']) + ); + if(! $x) + json_return_and_die($ret); + + \App::$data['chat']['uid'] = $x[0]['cr_uid']; + + if(! perm_is_allowed(\App::$data['chat']['uid'],get_observer_hash(),'chat')) { + json_return_and_die($ret); + } + + } + + function post() { + + $ret = array('success' => false); + + $room_id = \App::$data['chat']['room_id']; + $text = escape_tags($_REQUEST['chat_text']); + if(! $text) + return; + + $sql_extra = permissions_sql(\App::$data['chat']['uid']); + + $r = q("select * from chatroom where cr_uid = %d and cr_id = %d $sql_extra", + intval(\App::$data['chat']['uid']), + intval(\App::$data['chat']['room_id']) + ); + if(! $r) + json_return_and_die($ret); + + $arr = array( + 'chat_room' => \App::$data['chat']['room_id'], + 'chat_xchan' => get_observer_hash(), + 'chat_text' => $text + ); + + call_hooks('chat_post',$arr); + + $x = q("insert into chat ( chat_room, chat_xchan, created, chat_text ) + values( %d, '%s', '%s', '%s' )", + intval(\App::$data['chat']['room_id']), + dbesc(get_observer_hash()), + dbesc(datetime_convert()), + dbesc($arr['chat_text']) + ); + + $ret['success'] = true; + json_return_and_die($ret); + } + + function get() { + + $status = strip_tags($_REQUEST['status']); + $room_id = intval(\App::$data['chat']['room_id']); + $stopped = ((x($_REQUEST,'stopped') && intval($_REQUEST['stopped'])) ? true : false); + + if($status && $room_id) { + + $x = q("select channel_address from channel where channel_id = %d limit 1", + intval(\App::$data['chat']['uid']) + ); + + $r = q("update chatpresence set cp_status = '%s', cp_last = '%s' where cp_room = %d and cp_xchan = '%s' and cp_client = '%s'", + dbesc($status), + dbesc(datetime_convert()), + intval($room_id), + dbesc(get_observer_hash()), + dbesc($_SERVER['REMOTE_ADDR']) + ); + + goaway(z_root() . '/chat/' . $x[0]['channel_address'] . '/' . $room_id); + } + + if(! $stopped) { + + $lastseen = intval($_REQUEST['last']); + + $ret = array('success' => false); + + $sql_extra = permissions_sql(\App::$data['chat']['uid']); + + $r = q("select * from chatroom where cr_uid = %d and cr_id = %d $sql_extra", + intval(\App::$data['chat']['uid']), + intval(\App::$data['chat']['room_id']) + ); + if(! $r) + json_return_and_die($ret); + + $inroom = array(); + + $r = q("select * from chatpresence left join xchan on xchan_hash = cp_xchan where cp_room = %d order by xchan_name", + intval(\App::$data['chat']['room_id']) + ); + if($r) { + foreach($r as $rr) { + switch($rr['cp_status']) { + case 'away': + $status = t('Away'); + $status_class = 'away'; + break; + case 'online': + default: + $status = t('Online'); + $status_class = 'online'; + break; + } + + $inroom[] = array('img' => zid($rr['xchan_photo_m']), 'img_type' => $rr['xchan_photo_mimetype'],'name' => $rr['xchan_name'], 'status' => $status, 'status_class' => $status_class); + } + } + + $chats = array(); + + $r = q("select * from chat left join xchan on chat_xchan = xchan_hash where chat_room = %d and chat_id > %d order by created", + intval(\App::$data['chat']['room_id']), + intval($lastseen) + ); + if($r) { + foreach($r as $rr) { + $chats[] = array( + 'id' => $rr['chat_id'], + 'img' => zid($rr['xchan_photo_m']), + 'img_type' => $rr['xchan_photo_mimetype'], + 'name' => $rr['xchan_name'], + 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $rr['created'], 'c'), + 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $rr['created'], 'r'), + 'text' => smilies(bbcode($rr['chat_text'])), + 'self' => ((get_observer_hash() == $rr['chat_xchan']) ? 'self' : '') + ); + } + } + } + + $r = q("update chatpresence set cp_last = '%s' where cp_room = %d and cp_xchan = '%s' and cp_client = '%s'", + dbesc(datetime_convert()), + intval(\App::$data['chat']['room_id']), + dbesc(get_observer_hash()), + dbesc($_SERVER['REMOTE_ADDR']) + ); + + $ret['success'] = true; + if(! $stopped) { + $ret['inroom'] = $inroom; + $ret['chats'] = $chats; + } + json_return_and_die($ret); + + } + + +} diff --git a/Zotlabs/Module/Cloud.php b/Zotlabs/Module/Cloud.php new file mode 100644 index 000000000..68d84e070 --- /dev/null +++ b/Zotlabs/Module/Cloud.php @@ -0,0 +1,103 @@ + 1) + $which = argv(1); + + $profile = 0; + + \App::$page['htmlhead'] .= '' . "\r\n"; + + if ($which) + profile_load( $which, $profile); + + $auth = new \Zotlabs\Storage\BasicAuth(); + + $ob_hash = get_observer_hash(); + + if ($ob_hash) { + if (local_channel()) { + $channel = \App::get_channel(); + $auth->setCurrentUser($channel['channel_address']); + $auth->channel_id = $channel['channel_id']; + $auth->channel_hash = $channel['channel_hash']; + $auth->channel_account_id = $channel['channel_account_id']; + if($channel['channel_timezone']) + $auth->setTimezone($channel['channel_timezone']); + } + $auth->observer = $ob_hash; + } + + if ($_GET['davguest']) + $_SESSION['davguest'] = true; + + $_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']); + $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']); + $_SERVER['QUERY_STRING'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['QUERY_STRING']); + + $_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']); + $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); + $_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['REQUEST_URI']); + + $rootDirectory = new \Zotlabs\Storage\Directory('/', $auth); + + // A SabreDAV server-object + $server = new SDAV\Server($rootDirectory); + // prevent overwriting changes each other with a lock backend + $lockBackend = new SDAV\Locks\Backend\File('store/[data]/locks'); + $lockPlugin = new SDAV\Locks\Plugin($lockBackend); + + $server->addPlugin($lockPlugin); + + $is_readable = false; + + // provide a directory view for the cloud in Hubzilla + $browser = new \Zotlabs\Storage\Browser($auth); + $auth->setBrowserPlugin($browser); + + $server->addPlugin($browser); + + // Experimental QuotaPlugin + // require_once('\Zotlabs\Storage/QuotaPlugin.php'); + // $server->addPlugin(new \Zotlabs\Storage\\QuotaPlugin($auth)); + + ob_start(); + // All we need to do now, is to fire up the server + $server->exec(); + + ob_end_flush(); + + killme(); + } + +} diff --git a/Zotlabs/Module/Common.php b/Zotlabs/Module/Common.php new file mode 100644 index 000000000..2f3c57267 --- /dev/null +++ b/Zotlabs/Module/Common.php @@ -0,0 +1,73 @@ + 1 && intval(argv(1))) + $channel_id = intval(argv(1)); + else { + notice( t('No channel.') . EOL ); + \App::$error = 404; + return; + } + + $x = q("select channel_address from channel where channel_id = %d limit 1", + intval($channel_id) + ); + + if($x) + profile_load($x[0]['channel_address'],0); + + } + + function get() { + + $o = ''; + + if(! \App::$profile['profile_uid']) + return; + + $observer_hash = get_observer_hash(); + + + if(! perm_is_allowed(\App::$profile['profile_uid'],$observer_hash,'view_contacts')) { + notice( t('Permission denied.') . EOL); + return; + } + + $o .= '

' . t('Common connections') . '

'; + + $t = count_common_friends(\App::$profile['profile_uid'],$observer_hash); + + if(! $t) { + notice( t('No connections in common.') . EOL); + return $o; + } + + $r = common_friends(\App::$profile['profile_uid'],$observer_hash); + + if($r) { + + $tpl = get_markup_template('common_friends.tpl'); + + foreach($r as $rr) { + $o .= replace_macros($tpl,array( + '$url' => $rr['xchan_url'], + '$name' => $rr['xchan_name'], + '$photo' => $rr['xchan_photo_m'], + '$tags' => '' + )); + } + + $o .= cleardiv(); + } + + return $o; + } + +} diff --git a/Zotlabs/Module/Connect.php b/Zotlabs/Module/Connect.php new file mode 100644 index 000000000..962c05cce --- /dev/null +++ b/Zotlabs/Module/Connect.php @@ -0,0 +1,131 @@ + 1) + $which = argv(1); + else { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $r = q("select * from channel where channel_address = '%s' limit 1", + dbesc($which) + ); + + if($r) + \App::$data['channel'] = $r[0]; + + profile_load($which,''); + } + + function post() { + + if(! array_key_exists('channel', \App::$data)) + return; + + $edit = ((local_channel() && (local_channel() == \App::$data['channel']['channel_id'])) ? true : false); + + if($edit) { + $has_premium = ((\App::$data['channel']['channel_pageflags'] & PAGE_PREMIUM) ? 1 : 0); + $premium = (($_POST['premium']) ? intval($_POST['premium']) : 0); + $text = escape_tags($_POST['text']); + + if($has_premium != $premium) { + $r = q("update channel set channel_pageflags = ( channel_pageflags %s %d ) where channel_id = %d", + db_getfunc('^'), + intval(PAGE_PREMIUM), + intval(local_channel()) + ); + + \Zotlabs\Daemon\Master::Summon(array('Notifier','refresh_all',\App::$data['channel']['channel_id'])); + } + set_pconfig(\App::$data['channel']['channel_id'],'system','selltext',$text); + // reload the page completely to get fresh data + goaway(z_root() . '/' . \App::$query_string); + + } + + $url = ''; + $observer = \App::get_observer(); + if(($observer) && ($_POST['submit'] === t('Continue'))) { + if($observer['xchan_follow']) + $url = sprintf($observer['xchan_follow'],urlencode(\App::$data['channel']['channel_address'] . '@' . \App::get_hostname())); + if(! $url) { + $r = q("select * from hubloc where hubloc_hash = '%s' order by hubloc_id desc limit 1", + dbesc($observer['xchan_hash']) + ); + if($r) + $url = $r[0]['hubloc_url'] . '/follow?f=&url=' . urlencode(\App::$data['channel']['channel_address'] . '@' . \App::get_hostname()); + } + } + if($url) + goaway($url . '&confirm=1'); + else + notice('Unable to connect to your home hub location.'); + + } + + + + function get() { + + $edit = ((local_channel() && (local_channel() == \App::$data['channel']['channel_id'])) ? true : false); + + $text = get_pconfig(\App::$data['channel']['channel_id'],'system','selltext'); + + if($edit) { + + $o = replace_macros(get_markup_template('sellpage_edit.tpl'),array( + '$header' => t('Premium Channel Setup'), + '$address' => \App::$data['channel']['channel_address'], + '$premium' => array('premium', t('Enable premium channel connection restrictions'),((\App::$data['channel']['channel_pageflags'] & PAGE_PREMIUM) ? '1' : ''),''), + '$lbl_about' => t('Please enter your restrictions or conditions, such as paypal receipt, usage guidelines, etc.'), + '$text' => $text, + '$desc' => t('This channel may require additional steps or acknowledgement of the following conditions prior to connecting:'), + '$lbl2' => t('Potential connections will then see the following text before proceeding:'), + '$desc2' => t('By continuing, I certify that I have complied with any instructions provided on this page.'), + '$submit' => t('Submit'), + + + )); + return $o; + } + else { + if(! $text) + $text = t('(No specific instructions have been provided by the channel owner.)'); + + $submit = replace_macros(get_markup_template('sellpage_submit.tpl'), array( + '$continue' => t('Continue'), + '$address' => \App::$data['channel']['channel_address'] + )); + + $o = replace_macros(get_markup_template('sellpage_view.tpl'),array( + '$header' => t('Restricted or Premium Channel'), + '$desc' => t('This channel may require additional steps or acknowledgement of the following conditions prior to connecting:'), + '$text' => prepare_text($text), + + '$desc2' => t('By continuing, I certify that I have complied with any instructions provided on this page.'), + '$submit' => $submit, + + )); + + $arr = array('channel' => \App::$data['channel'],'observer' => \App::get_observer(), 'sellpage' => $o, 'submit' => $submit); + call_hooks('connect_premium', $arr); + $o = $arr['sellpage']; + + } + + return $o; + } +} diff --git a/Zotlabs/Module/Connections.php b/Zotlabs/Module/Connections.php new file mode 100644 index 000000000..a412d16ae --- /dev/null +++ b/Zotlabs/Module/Connections.php @@ -0,0 +1,324 @@ + t('Suggestions'), + 'url' => z_root() . '/suggest', + 'sel' => '', + 'title' => t('Suggest new connections'), + ), + */ + + 'pending' => array( + 'label' => t('New Connections'), + 'url' => z_root() . '/connections/pending', + 'sel' => ($pending) ? 'active' : '', + 'title' => t('Show pending (new) connections'), + ), + + 'all' => array( + 'label' => t('All Connections'), + 'url' => z_root() . '/connections/all', + 'sel' => ($all) ? 'active' : '', + 'title' => t('Show all connections'), + ), + + /* + array( + 'label' => t('Unblocked'), + 'url' => z_root() . '/connections', + 'sel' => (($unblocked) && (! $search) && (! $nets)) ? 'active' : '', + 'title' => t('Only show unblocked connections'), + ), + */ + + 'blocked' => array( + 'label' => t('Blocked'), + 'url' => z_root() . '/connections/blocked', + 'sel' => ($blocked) ? 'active' : '', + 'title' => t('Only show blocked connections'), + ), + + 'ignored' => array( + 'label' => t('Ignored'), + 'url' => z_root() . '/connections/ignored', + 'sel' => ($ignored) ? 'active' : '', + 'title' => t('Only show ignored connections'), + ), + + 'archived' => array( + 'label' => t('Archived'), + 'url' => z_root() . '/connections/archived', + 'sel' => ($archived) ? 'active' : '', + 'title' => t('Only show archived connections'), + ), + + 'hidden' => array( + 'label' => t('Hidden'), + 'url' => z_root() . '/connections/hidden', + 'sel' => ($hidden) ? 'active' : '', + 'title' => t('Only show hidden connections'), + ), + + // array( + // 'label' => t('Unconnected'), + // 'url' => z_root() . '/connections/unconnected', + // 'sel' => ($unconnected) ? 'active' : '', + // 'title' => t('Only show one-way connections'), + // ), + + + ); + + //$tab_tpl = get_markup_template('common_tabs.tpl'); + //$t = replace_macros($tab_tpl, array('$tabs'=>$tabs)); + + $searching = false; + if($search) { + $search_hdr = $search; + $search_txt = dbesc(protect_sprintf(preg_quote($search))); + $searching = true; + } + $sql_extra .= (($searching) ? protect_sprintf(" AND xchan_name like '%$search_txt%' ") : ""); + + if($_REQUEST['gid']) { + $sql_extra .= " and xchan_hash in ( select xchan from group_member where gid = " . intval($_REQUEST['gid']) . " and uid = " . intval(local_channel()) . " ) "; + } + + $r = q("SELECT COUNT(abook.abook_id) AS total FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash + where abook_channel = %d and abook_self = 0 and xchan_deleted = 0 and xchan_orphan = 0 $sql_extra $sql_extra2 ", + intval(local_channel()) + ); + if($r) { + \App::set_pager_total($r[0]['total']); + $total = $r[0]['total']; + } + + $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash + WHERE abook_channel = %d and abook_self = 0 and xchan_deleted = 0 and xchan_orphan = 0 $sql_extra $sql_extra2 ORDER BY xchan_name LIMIT %d OFFSET %d ", + intval(local_channel()), + intval(\App::$pager['itemspage']), + intval(\App::$pager['start']) + ); + + $contacts = array(); + + if(count($r)) { + + foreach($r as $rr) { + if($rr['xchan_url']) { + + $status_str = ''; + $status = array( + ((intval($rr['abook_pending'])) ? t('Pending approval') : ''), + ((intval($rr['abook_archived'])) ? t('Archived') : ''), + ((intval($rr['abook_hidden'])) ? t('Hidden') : ''), + ((intval($rr['abook_ignored'])) ? t('Ignored') : ''), + ((intval($rr['abook_blocked'])) ? t('Blocked') : '') + ); + + foreach($status as $str) { + if(!$str) + continue; + $status_str .= $str; + $status_str .= ', '; + } + $status_str = rtrim($status_str, ', '); + + $contacts[] = array( + 'img_hover' => sprintf( t('%1$s [%2$s]'),$rr['xchan_name'],$rr['xchan_url']), + 'edit_hover' => t('Edit connection'), + 'delete_hover' => t('Delete connection'), + 'id' => $rr['abook_id'], + 'thumb' => $rr['xchan_photo_m'], + 'name' => $rr['xchan_name'], + 'classes' => (intval($rr['abook_archived']) ? 'archived' : ''), + 'link' => z_root() . '/connedit/' . $rr['abook_id'], + 'deletelink' => z_root() . '/connedit/' . intval($rr['abook_id']) . '/drop', + 'delete' => t('Delete'), + 'url' => chanlink_url($rr['xchan_url']), + 'webbie_label' => t('Channel address'), + 'webbie' => $rr['xchan_addr'], + 'network_label' => t('Network'), + 'network' => network_to_name($rr['xchan_network']), + 'public_forum' => ((intval($rr['xchan_pubforum'])) ? true : false), + 'status_label' => t('Status'), + 'status' => $status_str, + 'connected_label' => t('Connected'), + 'connected' => datetime_convert('UTC',date_default_timezone_get(),$rr['abook_created'], 'c'), + 'approve_hover' => t('Approve connection'), + 'approve' => (($rr['abook_pending']) ? t('Approve') : false), + 'ignore_hover' => t('Ignore connection'), + 'ignore' => ((! $rr['abook_ignored']) ? t('Ignore') : false), + 'recent_label' => t('Recent activity'), + 'recentlink' => z_root() . '/network/?f=&cid=' . intval($rr['abook_id']) + ); + } + } + } + + + if($_REQUEST['aj']) { + if($contacts) { + $o = replace_macros(get_markup_template('contactsajax.tpl'),array( + '$contacts' => $contacts, + '$edit' => t('Edit'), + )); + } + else { + $o = '
'; + } + echo $o; + killme(); + } + else { + $o .= ""; + $o .= replace_macros(get_markup_template('connections.tpl'),array( + '$header' => t('Connections') . (($head) ? ': ' . $head : ''), + '$tabs' => $tabs, + '$total' => $total, + '$search' => $search_hdr, + '$label' => t('Search'), + '$desc' => t('Search your connections'), + '$finding' => (($searching) ? t('Connections search') . ": '" . $search . "'" : ""), + '$submit' => t('Find'), + '$edit' => t('Edit'), + '$cmd' => \App::$cmd, + '$contacts' => $contacts, + '$paginate' => paginate($a), + + )); + } + + if(! $contacts) + $o .= '
'; + + return $o; + } + +} diff --git a/Zotlabs/Module/Connedit.php b/Zotlabs/Module/Connedit.php new file mode 100644 index 000000000..93ee30999 --- /dev/null +++ b/Zotlabs/Module/Connedit.php @@ -0,0 +1,807 @@ += 2) && intval(argv(1))) { + $r = q("SELECT abook.*, xchan.* + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d and abook_id = %d LIMIT 1", + intval(local_channel()), + intval(argv(1)) + ); + if($r) { + \App::$poi = $r[0]; + } + } + + $channel = \App::get_channel(); + if($channel) + head_set_icon($channel['xchan_photo_s']); + + } + + /* @brief Evaluate posted values and set changes + * + */ + + function post() { + + if(! local_channel()) + return; + + $contact_id = intval(argv(1)); + if(! $contact_id) + return; + + $channel = \App::get_channel(); + + // TODO if configured for hassle-free permissions, we'll post the form with ajax as soon as the + // connection enable is toggled to a special autopost url and set permissions immediately, leaving + // the other form elements alone pending a manual submit of the form. The downside is that there + // will be a window of opportunity when the permissions have been set but before you've had a chance + // to review and possibly restrict them. The upside is we won't have to warn you that your connection + // can't do anything until you save the bloody form. + + $autopost = (((argc() > 2) && (argv(2) === 'auto')) ? true : false); + + $orig_record = q("SELECT * FROM abook WHERE abook_id = %d AND abook_channel = %d LIMIT 1", + intval($contact_id), + intval(local_channel()) + ); + + if(! $orig_record) { + notice( t('Could not access contact record.') . EOL); + goaway(z_root() . '/connections'); + return; // NOTREACHED + } + + call_hooks('contact_edit_post', $_POST); + + if(intval($orig_record[0]['abook_self'])) { + $autoperms = intval($_POST['autoperms']); + $is_self = true; + } + else { + $autoperms = null; + $is_self = false; + } + + + $profile_id = $_POST['profile_assign']; + if($profile_id) { + $r = q("SELECT profile_guid FROM profile WHERE profile_guid = '%s' AND `uid` = %d LIMIT 1", + dbesc($profile_id), + intval(local_channel()) + ); + if(! count($r)) { + notice( t('Could not locate selected profile.') . EOL); + return; + } + } + + $abook_incl = escape_tags($_POST['abook_incl']); + $abook_excl = escape_tags($_POST['abook_excl']); + + $hidden = intval($_POST['hidden']); + + $priority = intval($_POST['poll']); + if($priority > 5 || $priority < 0) + $priority = 0; + + $closeness = intval($_POST['closeness']); + if($closeness < 0) + $closeness = 99; + + $rating = intval($_POST['rating']); + if($rating < (-10)) + $rating = (-10); + if($rating > 10) + $rating = 10; + + $rating_text = trim(escape_tags($_REQUEST['rating_text'])); + + $all_perms = \Zotlabs\Access\Permissions::Perms(); + + if($all_perms) { + foreach($all_perms as $perm => $desc) { + if(array_key_exists('perms_' . $perm, $_POST)) { + set_abconfig($channel['channel_id'],$orig_record[0]['abook_xchan'],'my_perms',$perm, + intval($_POST['perms_' . $perm])); + if($autoperms) { + set_pconfig($channel['channel_id'],'autoperms',$perm,intval($_POST['perms_' . $perm])); + } + } + else { + set_abconfig($channel['channel_id'],$orig_record[0]['abook_xchan'],'my_perms',$perm,0); + if($autoperms) { + set_pconfig($channel['channel_id'],'autoperms',$perm,0); + } + } + } + } + + if(! is_null($autoperms)) + set_pconfig($channel['channel_id'],'system','autoperms',$autoperms); + + $new_friend = false; + + if(! $is_self) { + + $signed = $orig_record[0]['abook_xchan'] . '.' . $rating . '.' . $rating_text; + + $sig = base64url_encode(rsa_sign($signed,$channel['channel_prvkey'])); + + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc($orig_record[0]['abook_xchan']) + ); + + if($z) { + $record = $z[0]['xlink_id']; + $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' + where xlink_id = %d", + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()), + intval($record) + ); + } + else { + $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", + dbesc($channel['channel_hash']), + dbesc($orig_record[0]['abook_xchan']), + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()) + ); + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc($orig_record[0]['abook_xchan']) + ); + if($z) + $record = $z[0]['xlink_id']; + } + if($record) { + \Zotlabs\Daemon\Master::Summon(array('Ratenotif','rating',$record)); + } + } + + if(($_REQUEST['pending']) && intval($orig_record[0]['abook_pending'])) { + $new_friend = true; + + // @fixme it won't be common, but when you accept a new connection request + // the permissions will now be that of your permissions role and ignore + // any you may have set manually on the form. We'll probably see a bug if somebody + // tries to set the permissions *and* approve the connection in the same + // request. The workaround is to approve the connection, then go back and + // adjust permissions as desired. + + $abook_my_perms = get_channel_default_perms(local_channel()); + + $role = get_pconfig(local_channel(),'system','permissions_role'); + if($role) { + $x = \Zotlabs\Access\PermissionRoles::role_perms($role); + if($x['perms_connect']) { + $abook_my_perms = $x['perms_connect']; + } + } + + $filled_perms = \Zotlabs\Access\Permissions::FilledPerms($abook_my_perms); + foreach($filled_perms as $k => $v) { + set_abconfig($channel['channel_id'],$orig_record[0]['abook_xchan'],'my_perms',$k,$v); + } + + } + + $abook_pending = (($new_friend) ? 0 : $orig_record[0]['abook_pending']); + + $r = q("UPDATE abook SET abook_profile = '%s', abook_closeness = %d, abook_pending = %d, + abook_incl = '%s', abook_excl = '%s' + where abook_id = %d AND abook_channel = %d", + dbesc($profile_id), + intval($closeness), + intval($abook_pending), + dbesc($abook_incl), + dbesc($abook_excl), + intval($contact_id), + intval(local_channel()) + ); + + if($orig_record[0]['abook_profile'] != $profile_id) { + //Update profile photo permissions + + logger('A new profile was assigned - updating profile photos'); + profile_photo_set_profile_perms(local_channel(),$profile_id); + + } + + if($r) + info( t('Connection updated.') . EOL); + else + notice( t('Failed to update connection record.') . EOL); + + if(! intval(\App::$poi['abook_self'])) { + \Zotlabs\Daemon\Master::Summon( [ + 'Notifier', + (($new_friend) ? 'permission_create' : 'permission_update'), + $contact_id + ]); + } + + if($new_friend) { + $default_group = $channel['channel_default_group']; + if($default_group) { + require_once('include/group.php'); + $g = group_rec_byhash(local_channel(),$default_group); + if($g) + group_add_member(local_channel(),'',\App::$poi['abook_xchan'],$g['id']); + } + + // Check if settings permit ("post new friend activity" is allowed, and + // friends in general or this friend in particular aren't hidden) + // and send out a new friend activity + + $pr = q("select * from profile where uid = %d and is_default = 1 and hide_friends = 0", + intval($channel['channel_id']) + ); + if(($pr) && (! intval($orig_record[0]['abook_hidden'])) && (intval(get_pconfig($channel['channel_id'],'system','post_newfriend')))) { + $xarr = array(); + $xarr['verb'] = ACTIVITY_FRIEND; + $xarr['item_wall'] = 1; + $xarr['item_origin'] = 1; + $xarr['item_thread_top'] = 1; + $xarr['owner_xchan'] = $xarr['author_xchan'] = $channel['channel_hash']; + $xarr['allow_cid'] = $channel['channel_allow_cid']; + $xarr['allow_gid'] = $channel['channel_allow_gid']; + $xarr['deny_cid'] = $channel['channel_deny_cid']; + $xarr['deny_gid'] = $channel['channel_deny_gid']; + $xarr['item_private'] = (($xarr['allow_cid']||$xarr['allow_gid']||$xarr['deny_cid']||$xarr['deny_gid']) ? 1 : 0); + $obj = array( + 'type' => ACTIVITY_OBJ_PERSON, + 'title' => \App::$poi['xchan_name'], + 'id' => \App::$poi['xchan_hash'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => \App::$poi['xchan_url']), + array('rel' => 'photo', 'type' => \App::$poi['xchan_photo_mimetype'], 'href' => \App::$poi['xchan_photo_l']) + ), + ); + $xarr['obj'] = json_encode($obj); + $xarr['obj_type'] = ACTIVITY_OBJ_PERSON; + + $xarr['body'] = '[zrl=' . $channel['xchan_url'] . ']' . $channel['xchan_name'] . '[/zrl]' . ' ' . t('is now connected to') . ' ' . '[zrl=' . \App::$poi['xchan_url'] . ']' . \App::$poi['xchan_name'] . '[/zrl]'; + + $xarr['body'] .= "\n\n\n" . '[zrl=' . \App::$poi['xchan_url'] . '][zmg=80x80]' . \App::$poi['xchan_photo_m'] . '[/zmg][/zrl]'; + + post_activity_item($xarr); + + } + + + // pull in a bit of content if there is any to pull in + \Zotlabs\Daemon\Master::Summon(array('Onepoll',$contact_id)); + + } + + // Refresh the structure in memory with the new data + + $r = q("SELECT abook.*, xchan.* + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d and abook_id = %d LIMIT 1", + intval(local_channel()), + intval($contact_id) + ); + if($r) { + \App::$poi = $r[0]; + } + + if($new_friend) { + $arr = array('channel_id' => local_channel(), 'abook' => \App::$poi); + call_hooks('accept_follow', $arr); + } + + if(! is_null($autoperms)) + set_pconfig(local_channel(),'system','autoperms',(($autoperms) ? $abook_my_perms : 0)); + + $this->connedit_clone($a); + + if(($_REQUEST['pending']) && (!$_REQUEST['done'])) + goaway(z_root() . '/connections/ifpending'); + + return; + + } + + /* @brief Clone connection + * + * + */ + + function connedit_clone(&$a) { + + if(! \App::$poi) + return; + + + $channel = \App::get_channel(); + + $r = q("SELECT abook.*, xchan.* + FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d and abook_id = %d LIMIT 1", + intval(local_channel()), + intval(\App::$poi['abook_id']) + ); + if($r) { + \App::$poi = $r[0]; + } + + $clone = \App::$poi; + + unset($clone['abook_id']); + unset($clone['abook_account']); + unset($clone['abook_channel']); + + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); + if($abconfig) + $clone['abconfig'] = $abconfig; + + build_sync_packet(0 /* use the current local_channel */, array('abook' => array($clone))); + } + + /* @brief Generate content of connection edit page + * + * + */ + + function get() { + + $sort_type = 0; + $o = ''; + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return login(); + } + + $channel = \App::get_channel(); + $my_perms = get_channel_default_perms(local_channel()); + $role = get_pconfig(local_channel(),'system','permissions_role'); + if($role) { + $x = \Zotlabs\Access\PermissionRoles::role_perms($role); + if($x['perms_connect']) + $my_perms = $x['perms_connect']; + } + + $yes_no = array(t('No'),t('Yes')); + + if($my_perms) { + $o .= "\n"; + } + + if(argc() == 3) { + + $contact_id = intval(argv(1)); + if(! $contact_id) + return; + + $cmd = argv(2); + + $orig_record = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_id = %d AND abook_channel = %d AND abook_self = 0 LIMIT 1", + intval($contact_id), + intval(local_channel()) + ); + + if(! count($orig_record)) { + notice( t('Could not access address book record.') . EOL); + goaway(z_root() . '/connections'); + } + + if($cmd === 'update') { + // pull feed and consume it, which should subscribe to the hub. + \Zotlabs\Daemon\Master::Summon(array('Poller',$contact_id)); + goaway(z_root() . '/connedit/' . $contact_id); + + } + if($cmd === 'resetphoto') { + q("update xchan set xchan_photo_date = '2001-01-01 00:00:00' where xchan_hash = '%s' limit 1", + dbesc($orig_record[0]['xchan_hash']) + ); + $cmd = 'refresh'; + } + + if($cmd === 'refresh') { + if($orig_record[0]['xchan_network'] === 'zot') { + if(! zot_refresh($orig_record[0],\App::get_channel())) + notice( t('Refresh failed - channel is currently unavailable.') ); + } + else { + + // if you are on a different network we'll force a refresh of the connection basic info + \Zotlabs\Daemon\Master::Summon(array('Notifier','permission_update',$contact_id)); + } + goaway(z_root() . '/connedit/' . $contact_id); + } + + if($cmd === 'block') { + if(abook_toggle_flag($orig_record[0],ABOOK_FLAG_BLOCKED)) { + $this->connedit_clone($a); + } + else + notice(t('Unable to set address book parameters.') . EOL); + goaway(z_root() . '/connedit/' . $contact_id); + } + + if($cmd === 'ignore') { + if(abook_toggle_flag($orig_record[0],ABOOK_FLAG_IGNORED)) { + $this->connedit_clone($a); + } + else + notice(t('Unable to set address book parameters.') . EOL); + goaway(z_root() . '/connedit/' . $contact_id); + } + + if($cmd === 'archive') { + if(abook_toggle_flag($orig_record[0],ABOOK_FLAG_ARCHIVED)) { + $this->connedit_clone($a); + } + else + notice(t('Unable to set address book parameters.') . EOL); + goaway(z_root() . '/connedit/' . $contact_id); + } + + if($cmd === 'hide') { + if(abook_toggle_flag($orig_record[0],ABOOK_FLAG_HIDDEN)) { + $this->connedit_clone($a); + } + else + notice(t('Unable to set address book parameters.') . EOL); + goaway(z_root() . '/connedit/' . $contact_id); + } + + // We'll prevent somebody from unapproving an already approved contact. + // Though maybe somebody will want this eventually (??) + + if($cmd === 'approve') { + if(intval($orig_record[0]['abook_pending'])) { + if(abook_toggle_flag($orig_record[0],ABOOK_FLAG_PENDING)) { + $this->connedit_clone($a); + } + else + notice(t('Unable to set address book parameters.') . EOL); + } + goaway(z_root() . '/connedit/' . $contact_id); + } + + + if($cmd === 'drop') { + + + // FIXME + // We need to send either a purge or a refresh packet to the other side (the channel being unfriended). + // The issue is that the abook DB record _may_ get destroyed when we call contact_remove. As the notifier runs + // in the background there could be a race condition preventing this packet from being sent in all cases. + // PLACEHOLDER + + contact_remove(local_channel(), $orig_record[0]['abook_id']); + build_sync_packet(0 /* use the current local_channel */, + array('abook' => array(array( + 'abook_xchan' => $orig_record[0]['abook_xchan'], + 'entry_deleted' => true)) + ) + ); + + info( t('Connection has been removed.') . EOL ); + if(x($_SESSION,'return_url')) + goaway(z_root() . '/' . $_SESSION['return_url']); + goaway(z_root() . '/contacts'); + + } + } + + if(\App::$poi) { + + $contact_id = \App::$poi['abook_id']; + $contact = \App::$poi; + + $tools = array( + + 'view' => array( + 'label' => t('View Profile'), + 'url' => chanlink_cid($contact['abook_id']), + 'sel' => '', + 'title' => sprintf( t('View %s\'s profile'), $contact['xchan_name']), + ), + + 'refresh' => array( + 'label' => t('Refresh Permissions'), + 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/refresh', + 'sel' => '', + 'title' => t('Fetch updated permissions'), + ), + + 'recent' => array( + 'label' => t('Recent Activity'), + 'url' => z_root() . '/network/?f=&cid=' . $contact['abook_id'], + 'sel' => '', + 'title' => t('View recent posts and comments'), + ), + + 'block' => array( + 'label' => (intval($contact['abook_blocked']) ? t('Unblock') : t('Block')), + 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/block', + 'sel' => (intval($contact['abook_blocked']) ? 'active' : ''), + 'title' => t('Block (or Unblock) all communications with this connection'), + 'info' => (intval($contact['abook_blocked']) ? t('This connection is blocked!') : ''), + ), + + 'ignore' => array( + 'label' => (intval($contact['abook_ignored']) ? t('Unignore') : t('Ignore')), + 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/ignore', + 'sel' => (intval($contact['abook_ignored']) ? 'active' : ''), + 'title' => t('Ignore (or Unignore) all inbound communications from this connection'), + 'info' => (intval($contact['abook_ignored']) ? t('This connection is ignored!') : ''), + ), + + 'archive' => array( + 'label' => (intval($contact['abook_archived']) ? t('Unarchive') : t('Archive')), + 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/archive', + 'sel' => (intval($contact['abook_archived']) ? 'active' : ''), + 'title' => t('Archive (or Unarchive) this connection - mark channel dead but keep content'), + 'info' => (intval($contact['abook_archived']) ? t('This connection is archived!') : ''), + ), + + 'hide' => array( + 'label' => (intval($contact['abook_hidden']) ? t('Unhide') : t('Hide')), + 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/hide', + 'sel' => (intval($contact['abook_hidden']) ? 'active' : ''), + 'title' => t('Hide or Unhide this connection from your other connections'), + 'info' => (intval($contact['abook_hidden']) ? t('This connection is hidden!') : ''), + ), + + 'delete' => array( + 'label' => t('Delete'), + 'url' => z_root() . '/connedit/' . $contact['abook_id'] . '/drop', + 'sel' => '', + 'title' => t('Delete this connection'), + ), + + ); + + $self = false; + + if(intval($contact['abook_self'])) + $self = true; + + $tpl = get_markup_template("abook_edit.tpl"); + + if(feature_enabled(local_channel(),'affinity')) { + + $labels = array( + t('Me'), + t('Family'), + t('Friends'), + t('Acquaintances'), + t('All') + ); + call_hooks('affinity_labels',$labels); + $label_str = ''; + + if($labels) { + foreach($labels as $l) { + if($label_str) { + $label_str .= ", '|'"; + $label_str .= ", '" . $l . "'"; + } + else + $label_str .= "'" . $l . "'"; + } + } + + $slider_tpl = get_markup_template('contact_slider.tpl'); + $slide = replace_macros($slider_tpl,array( + '$min' => 1, + '$val' => (($contact['abook_closeness']) ? $contact['abook_closeness'] : 99), + '$labels' => $label_str, + )); + } + + $rating_val = 0; + $rating_text = ''; + + $xl = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", + dbesc($channel['channel_hash']), + dbesc($contact['xchan_hash']) + ); + + if($xl) { + $rating_val = intval($xl[0]['xlink_rating']); + $rating_text = $xl[0]['xlink_rating_text']; + } + + $poco_rating = get_config('system','poco_rating_enable'); + + // if unset default to enabled + if($poco_rating === false) + $poco_rating = true; + + if($poco_rating) { + $rating = replace_macros(get_markup_template('rating_slider.tpl'),array( + '$min' => -10, + '$val' => $rating_val + )); + } + else { + $rating = false; + } + + + $perms = array(); + $channel = \App::get_channel(); + + $global_perms = \Zotlabs\Access\Permissions::Perms(); + + $existing = get_all_perms(local_channel(),$contact['abook_xchan']); + + $unapproved = array('pending', t('Approve this connection'), '', t('Accept connection to allow communication'), array(t('No'),('Yes'))); + + $multiprofs = ((feature_enabled(local_channel(),'multi_profiles')) ? true : false); + + if($slide && !$multiprofs) + $affinity = t('Set Affinity'); + + if(!$slide && $multiprofs) + $affinity = t('Set Profile'); + + if($slide && $multiprofs) + $affinity = t('Set Affinity & Profile'); + + $theirs = q("select * from abconfig where chan = %d and xchan = '%s' and cat = 'their_perms'", + intval(local_channel()), + dbesc($contact['abook_xchan']) + ); + $their_perms = array(); + if($theirs) { + foreach($theirs as $t) { + $their_perms[$t['k']] = $t['v']; + } + } + + foreach($global_perms as $k => $v) { + $thisperm = get_abconfig(local_channel(),$contact['abook_xchan'],'my_perms',$k); +//fixme + + $checkinherited = \Zotlabs\Access\PermissionLimits::Get(local_channel(),$k); + + // For auto permissions (when $self is true) we don't want to look at existing + // permissions because they are enabled for the channel owner + if((! $self) && ($existing[$k])) + $thisperm = "1"; + + + + + $perms[] = array('perms_' . $k, $v, ((array_key_exists($k,$their_perms)) ? intval($their_perms[$k]) : ''),$thisperm, 1, (($checkinherited & PERMS_SPECIFIC) ? '' : '1'), '', $checkinherited); + } + + $locstr = ''; + + $locs = q("select hubloc_addr as location from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' + and hubloc_deleted = 0 and site_dead = 0", + dbesc($contact['xchan_hash']) + ); + + if($locs) { + foreach($locs as $l) { + if(!($l['location'])) + continue; + if(strpos($locstr,$l['location']) !== false) + continue; + if(strlen($locstr)) + $locstr .= ', '; + $locstr .= $l['location']; + } + } + else + $locstr = t('none'); + + $o .= replace_macros($tpl,array( + + '$header' => (($self) ? t('Connection Default Permissions') : sprintf( t('Connection: %s'),$contact['xchan_name'])), + '$autoperms' => array('autoperms',t('Apply these permissions automatically'), ((get_pconfig(local_channel(),'system','autoperms')) ? 1 : 0), t('Connection requests will be approved without your interaction'), $yes_no), + '$addr' => $contact['xchan_addr'], + '$addr_text' => t('This connection\'s primary address is'), + '$loc_text' => t('Available locations:'), + '$locstr' => $locstr, + '$notself' => (($self) ? '' : '1'), + '$self' => (($self) ? '1' : ''), + '$autolbl' => t('The permissions indicated on this page will be applied to all new connections.'), + '$tools_label' => t('Connection Tools'), + '$tools' => (($self) ? '' : $tools), + '$lbl_slider' => t('Slide to adjust your degree of friendship'), + '$lbl_rating' => t('Rating'), + '$lbl_rating_label' => t('Slide to adjust your rating'), + '$lbl_rating_txt' => t('Optionally explain your rating'), + '$connfilter' => feature_enabled(local_channel(),'connfilter'), + '$connfilter_label' => t('Custom Filter'), + '$incl' => array('abook_incl',t('Only import posts with this text'), $contact['abook_incl'],t('words one per line or #tags or /patterns/ or lang=xx, leave blank to import all posts')), + '$excl' => array('abook_excl',t('Do not import posts with this text'), $contact['abook_excl'],t('words one per line or #tags or /patterns/ or lang=xx, leave blank to import all posts')), + '$rating_text' => array('rating_text', t('Optionally explain your rating'),$rating_text,''), + '$rating_info' => t('This information is public!'), + '$rating' => $rating, + '$rating_val' => $rating_val, + '$slide' => $slide, + '$affinity' => $affinity, + '$pending_label' => t('Connection Pending Approval'), + '$is_pending' => (intval($contact['abook_pending']) ? 1 : ''), + '$unapproved' => $unapproved, + '$inherited' => t('inherited'), + '$submit' => t('Submit'), + '$lbl_vis2' => sprintf( t('Please choose the profile you would like to display to %s when viewing your profile securely.'), $contact['xchan_name']), + '$close' => $contact['abook_closeness'], + '$them' => t('Their Settings'), + '$me' => t('My Settings'), + '$perms' => $perms, + '$permlbl' => t('Individual Permissions'), + '$permnote' => t('Some permissions may be inherited from your channel\'s privacy settings, which have higher priority than individual settings. You can not change those settings here.'), + '$permnote_self' => t('Some permissions may be inherited from your channel\'s privacy settings, which have higher priority than individual settings. You can change those settings here but they wont have any impact unless the inherited setting changes.'), + '$lastupdtext' => t('Last update:'), + '$last_update' => relative_date($contact['abook_connected']), + '$profile_select' => contact_profile_assign($contact['abook_profile']), + '$multiprofs' => $multiprofs, + '$contact_id' => $contact['abook_id'], + '$name' => $contact['xchan_name'], + + )); + + $arr = array('contact' => $contact,'output' => $o); + + call_hooks('contact_edit', $arr); + + return $arr['output']; + + } + + + } + +} diff --git a/Zotlabs/Module/Contactgroup.php b/Zotlabs/Module/Contactgroup.php new file mode 100644 index 000000000..bbe56b4ad --- /dev/null +++ b/Zotlabs/Module/Contactgroup.php @@ -0,0 +1,54 @@ + 2) && (intval(argv(1))) && (argv(2))) { + $r = q("SELECT abook_xchan from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", + dbesc(base64url_decode(argv(2))), + intval(local_channel()) + ); + if($r) + $change = $r[0]['abook_xchan']; + } + + if((argc() > 1) && (intval(argv(1)))) { + + $r = q("SELECT * FROM `groups` WHERE `id` = %d AND `uid` = %d AND `deleted` = 0 LIMIT 1", + intval(argv(1)), + intval(local_channel()) + ); + if(! $r) { + killme(); + } + + $group = $r[0]; + $members = group_get_members($group['id']); + $preselected = array(); + if(count($members)) { + foreach($members as $member) + $preselected[] = $member['xchan_hash']; + } + + if($change) { + if(in_array($change,$preselected)) { + group_rmv_member(local_channel(),$group['gname'],$change); + } + else { + group_add_member(local_channel(),$group['gname'],$change); + } + } + } + + killme(); + } +} diff --git a/Zotlabs/Module/Cover_photo.php b/Zotlabs/Module/Cover_photo.php new file mode 100644 index 000000000..886958b37 --- /dev/null +++ b/Zotlabs/Module/Cover_photo.php @@ -0,0 +1,423 @@ +is_valid()) { + + // We are scaling and cropping the relative pixel locations to the original photo instead of the + // scaled photo we operated on. + + // First load the scaled photo to check its size. (Should probably pass this in the post form and save + // a query.) + + $g = q("select width, height from photo where resource_id = '%s' and uid = %d and imgscale = 3", + dbesc($image_id), + intval(local_channel()) + ); + + + $scaled_width = $g[0]['width']; + $scaled_height = $g[0]['height']; + + if((! $scaled_width) || (! $scaled_height)) { + logger('potential divide by zero scaling cover photo'); + return; + } + + // unset all other cover photos + + q("update photo set photo_usage = %d where photo_usage = %d and uid = %d", + intval(PHOTO_NORMAL), + intval(PHOTO_COVER), + intval(local_channel()) + ); + + $orig_srcx = ( $r[0]['width'] / $scaled_width ) * $srcX; + $orig_srcy = ( $r[0]['height'] / $scaled_height ) * $srcY; + $orig_srcw = ( $srcW / $scaled_width ) * $r[0]['width']; + $orig_srch = ( $srcH / $scaled_height ) * $r[0]['height']; + + $im->cropImageRect(1200,435,$orig_srcx, $orig_srcy, $orig_srcw, $orig_srch); + + $aid = get_account_id(); + + $p = array('aid' => $aid, 'uid' => local_channel(), 'resource_id' => $base_image['resource_id'], + 'filename' => $base_image['filename'], 'album' => t('Cover Photos')); + + $p['imgscale'] = 7; + $p['photo_usage'] = PHOTO_COVER; + + $r1 = $im->save($p); + + $im->doScaleImage(850,310); + $p['imgscale'] = 8; + + $r2 = $im->save($p); + + + $im->doScaleImage(425,160); + $p['imgscale'] = 9; + + $r3 = $im->save($p); + + if($r1 === false || $r2 === false || $r3 === false) { + // if one failed, delete them all so we can start over. + notice( t('Image resize failed.') . EOL ); + $x = q("delete from photo where resource_id = '%s' and uid = %d and imgscale >= 7 ", + dbesc($base_image['resource_id']), + local_channel() + ); + return; + } + + $channel = \App::get_channel(); + $this->send_cover_photo_activity($channel,$base_image,$profile); + + + } + else + notice( t('Unable to process image') . EOL); + } + + goaway(z_root() . '/channel/' . $channel['channel_address']); + + } + + + $hash = photo_new_resource(); + $smallest = 0; + + require_once('include/attach.php'); + + $res = attach_store(\App::get_channel(), get_observer_hash(), '', array('album' => t('Cover Photos'), 'hash' => $hash)); + + logger('attach_store: ' . print_r($res,true)); + + if($res && intval($res['data']['is_photo'])) { + $i = q("select * from photo where resource_id = '%s' and uid = %d and imgscale = 0", + dbesc($hash), + intval(local_channel()) + ); + + if(! $i) { + notice( t('Image upload failed.') . EOL ); + return; + } + $os_storage = false; + + foreach($i as $ii) { + $smallest = intval($ii['imgscale']); + $os_storage = intval($ii['os_storage']); + $imagedata = $ii['content']; + $filetype = $ii['mimetype']; + + } + } + + $imagedata = (($os_storage) ? @file_get_contents($imagedata) : $imagedata); + $ph = photo_factory($imagedata, $filetype); + + if(! $ph->is_valid()) { + notice( t('Unable to process image.') . EOL ); + return; + } + + return $this->cover_photo_crop_ui_head($a, $ph, $hash, $smallest); + + } + + function send_cover_photo_activity($channel,$photo,$profile) { + + $arr = array(); + $arr['item_thread_top'] = 1; + $arr['item_origin'] = 1; + $arr['item_wall'] = 1; + $arr['obj_type'] = ACTIVITY_OBJ_PHOTO; + $arr['verb'] = ACTIVITY_UPDATE; + + $arr['obj'] = json_encode(array( + 'type' => $arr['obj_type'], + 'id' => z_root() . '/photo/' . $photo['resource_id'] . '-7', + 'link' => array('rel' => 'photo', 'type' => $photo['mimetype'], 'href' => z_root() . '/photo/' . $photo['resource_id'] . '-7') + )); + + if($profile && stripos($profile['gender'],t('female')) !== false) + $t = t('%1$s updated her %2$s'); + elseif($profile && stripos($profile['gender'],t('male')) !== false) + $t = t('%1$s updated his %2$s'); + else + $t = t('%1$s updated their %2$s'); + + $ptext = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' . t('cover photo') . '[/zrl]'; + + $ltext = '[zrl=' . z_root() . '/profile/' . $channel['channel_address'] . ']' . '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-8[/zmg][/zrl]'; + + $arr['body'] = sprintf($t,$channel['channel_name'],$ptext) . "\n\n" . $ltext; + + $acl = new \Zotlabs\Access\AccessList($channel); + $x = $acl->get(); + $arr['allow_cid'] = $x['allow_cid']; + + $arr['allow_gid'] = $x['allow_gid']; + $arr['deny_cid'] = $x['deny_cid']; + $arr['deny_gid'] = $x['deny_gid']; + + $arr['uid'] = $channel['channel_id']; + $arr['aid'] = $channel['channel_account_id']; + + $arr['owner_xchan'] = $channel['channel_hash']; + $arr['author_xchan'] = $channel['channel_hash']; + + post_activity_item($arr); + + + } + + + /* @brief Generate content of profile-photo view + * + * @param $a Current application + * @return void + * + */ + + + function get() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL ); + return; + } + + $channel = \App::get_channel(); + + $newuser = false; + + if(argc() == 2 && argv(1) === 'new') + $newuser = true; + + if(argv(1) === 'use') { + if (argc() < 3) { + notice( t('Permission denied.') . EOL ); + return; + }; + + // check_form_security_token_redirectOnErr('/cover_photo', 'cover_photo'); + + $resource_id = argv(2); + + $r = q("SELECT id, album, imgscale FROM photo WHERE uid = %d AND resource_id = '%s' ORDER BY imgscale ASC", + intval(local_channel()), + dbesc($resource_id) + ); + if(! $r) { + notice( t('Photo not available.') . EOL ); + return; + } + $havescale = false; + foreach($r as $rr) { + if($rr['imgscale'] == 7) + $havescale = true; + } + + $r = q("SELECT `content`, `mimetype`, resource_id, os_storage FROM photo WHERE id = %d and uid = %d limit 1", + intval($r[0]['id']), + intval(local_channel()) + + ); + if(! $r) { + notice( t('Photo not available.') . EOL ); + return; + } + + if(intval($r[0]['os_storage'])) + $data = @file_get_contents($r[0]['content']); + else + $data = dbunescbin($r[0]['content']); + + $ph = photo_factory($data, $r[0]['mimetype']); + $smallest = 0; + if($ph->is_valid()) { + // go ahead as if we have just uploaded a new photo to crop + $i = q("select resource_id, imgscale from photo where resource_id = '%s' and uid = %d and imgscale = 0", + dbesc($r[0]['resource_id']), + intval(local_channel()) + ); + + if($i) { + $hash = $i[0]['resource_id']; + foreach($i as $ii) { + $smallest = intval($ii['imgscale']); + } + } + } + + $this->cover_photo_crop_ui_head($a, $ph, $hash, $smallest); + } + + + if(! x(\App::$data,'imagecrop')) { + + $tpl = get_markup_template('cover_photo.tpl'); + + $o .= replace_macros($tpl,array( + '$user' => \App::$channel['channel_address'], + '$lbl_upfile' => t('Upload File:'), + '$lbl_profiles' => t('Select a profile:'), + '$title' => t('Upload Cover Photo'), + '$submit' => t('Upload'), + '$profiles' => $profiles, + '$form_security_token' => get_form_security_token("cover_photo"), + // FIXME - yuk + '$select' => sprintf('%s %s', t('or'), ($newuser) ? '' . t('skip this step') . '' : '' . t('select a photo from your photo albums') . '') + )); + + call_hooks('cover_photo_content_end', $o); + + return $o; + } + else { + $filename = \App::$data['imagecrop'] . '-3'; + $resolution = 3; + $tpl = get_markup_template("cropcover.tpl"); + $o .= replace_macros($tpl,array( + '$filename' => $filename, + '$profile' => intval($_REQUEST['profile']), + '$resource' => \App::$data['imagecrop'] . '-3', + '$image_url' => z_root() . '/photo/' . $filename, + '$title' => t('Crop Image'), + '$desc' => t('Please adjust the image cropping for optimum viewing.'), + '$form_security_token' => get_form_security_token("cover_photo"), + '$done' => t('Done Editing') + )); + return $o; + } + + return; // NOTREACHED + } + + /* @brief Generate the UI for photo-cropping + * + * @param $a Current application + * @param $ph Photo-Factory + * @return void + * + */ + + + + function cover_photo_crop_ui_head(&$a, $ph, $hash, $smallest){ + + $max_length = get_config('system','max_image_length'); + if(! $max_length) + $max_length = MAX_IMAGE_LENGTH; + if($max_length > 0) + $ph->scaleImage($max_length); + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + if($width < 300 || $height < 300) { + $ph->scaleImageUp(240); + $width = $ph->getWidth(); + $height = $ph->getHeight(); + } + + + \App::$data['imagecrop'] = $hash; + \App::$data['imagecrop_resolution'] = $smallest; + \App::$page['htmlhead'] .= replace_macros(get_markup_template("crophead.tpl"), array()); + return; + } + + +} diff --git a/Zotlabs/Module/Dav.php b/Zotlabs/Module/Dav.php new file mode 100644 index 000000000..aaf69844c --- /dev/null +++ b/Zotlabs/Module/Dav.php @@ -0,0 +1,88 @@ + 1) + profile_load(argv(1),0); + + + $auth = new \Zotlabs\Storage\BasicAuth(); + $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . ' ' . 'WebDAV'); + + $rootDirectory = new \Zotlabs\Storage\Directory('/', $auth); + + // A SabreDAV server-object + $server = new SDAV\Server($rootDirectory); + + + $authPlugin = new \Sabre\DAV\Auth\Plugin($auth); + $server->addPlugin($authPlugin); + + + // prevent overwriting changes each other with a lock backend + $lockBackend = new SDAV\Locks\Backend\File('store/[data]/locks'); + $lockPlugin = new SDAV\Locks\Plugin($lockBackend); + + $server->addPlugin($lockPlugin); + + // provide a directory view for the cloud in Hubzilla + $browser = new \Zotlabs\Storage\Browser($auth); + $auth->setBrowserPlugin($browser); + + // Experimental QuotaPlugin + // require_once('Zotlabs/Storage/QuotaPlugin.php'); + // $server->addPlugin(new \Zotlabs\Storage\QuotaPlugin($auth)); + + // All we need to do now, is to fire up the server + $server->exec(); + + killme(); + } + +} diff --git a/Zotlabs/Module/Directory.php b/Zotlabs/Module/Directory.php new file mode 100644 index 000000000..560038ffc --- /dev/null +++ b/Zotlabs/Module/Directory.php @@ -0,0 +1,425 @@ + 0) ? intval($numtags) : 50); + + if(get_config('system','disable_directory_keywords')) + $kw = 0; + + $query = $url . '?f=&kw=' . $kw . (($safe_mode != 1) ? '&safe=' . $safe_mode : ''); + + if($token) + $query .= '&t=' . $token; + + if(! $globaldir) + $query .= '&hub=' . \App::get_hostname(); + + if($search) + $query .= '&name=' . urlencode($search) . '&keywords=' . urlencode($search); + if(strpos($search,'@')) + $query .= '&address=' . urlencode($search); + if($keywords) + $query .= '&keywords=' . urlencode($keywords); + if($advanced) + $query .= '&query=' . urlencode($advanced); + if(! is_null($pubforums)) + $query .= '&pubforums=' . intval($pubforums); + + $directory_sort_order = get_config('system','directory_sort_order'); + if(! $directory_sort_order) + $directory_sort_order = 'date'; + + $sort_order = ((x($_REQUEST,'order')) ? $_REQUEST['order'] : $directory_sort_order); + + if($sort_order) + $query .= '&order=' . urlencode($sort_order); + + if(\App::$pager['page'] != 1) + $query .= '&p=' . \App::$pager['page']; + + logger('mod_directory: query: ' . $query); + + $x = z_fetch_url($query); + logger('directory: return from upstream: ' . print_r($x,true), LOGGER_DATA); + + if($x['success']) { + $t = 0; + $j = json_decode($x['body'],true); + if($j) { + + if($j['results']) { + + $entries = array(); + + $photo = 'thumb'; + + foreach($j['results'] as $rr) { + + $profile_link = chanlink_url($rr['url']); + + $pdesc = (($rr['description']) ? $rr['description'] . '
' : ''); + $connect_link = ((local_channel()) ? z_root() . '/follow?f=&url=' . urlencode($rr['address']) : ''); + + // Checking status is disabled ATM until someone checks the performance impact more carefully + //$online = remote_online_status($rr['address']); + $online = ''; + + if(in_array($rr['hash'],$contacts)) + $connect_link = ''; + + $location = ''; + if(strlen($rr['locale'])) + $location .= $rr['locale']; + if(strlen($rr['region'])) { + if(strlen($rr['locale'])) + $location .= ', '; + $location .= $rr['region']; + } + if(strlen($rr['country'])) { + if(strlen($location)) + $location .= ', '; + $location .= $rr['country']; + } + + $age = ''; + if(strlen($rr['birthday'])) { + if(($years = age($rr['birthday'],'UTC','')) != 0) + $age = $years; + } + + $page_type = ''; + + if($rr['total_ratings']) + $total_ratings = sprintf( tt("%d rating", "%d ratings", $rr['total_ratings']), $rr['total_ratings']); + else + $total_ratings = ''; + + $profile = $rr; + + if ((x($profile,'locale') == 1) + || (x($profile,'region') == 1) + || (x($profile,'postcode') == 1) + || (x($profile,'country') == 1)) + + $gender = ((x($profile,'gender') == 1) ? t('Gender: ') . $profile['gender']: False); + + $marital = ((x($profile,'marital') == 1) ? t('Status: ') . $profile['marital']: False); + + $homepage = ((x($profile,'homepage') == 1) ? t('Homepage: ') : False); + $homepageurl = ((x($profile,'homepage') == 1) ? $profile['homepage'] : ''); + + $hometown = ((x($profile,'hometown') == 1) ? $profile['hometown'] : False); + + $about = ((x($profile,'about') == 1) ? bbcode($profile['about']) : False); + + $keywords = ((x($profile,'keywords')) ? $profile['keywords'] : ''); + + $out = ''; + + if($keywords) { + $keywords = str_replace(',',' ', $keywords); + $keywords = str_replace(' ',' ', $keywords); + $karr = explode(' ', $keywords); + + if($karr) { + if(local_channel()) { + $r = q("select keywords from profile where uid = %d and is_default = 1 limit 1", + intval(local_channel()) + ); + if($r) { + $keywords = str_replace(',',' ', $r[0]['keywords']); + $keywords = str_replace(' ',' ', $keywords); + $marr = explode(' ', $keywords); + } + } + foreach($karr as $k) { + if(strlen($out)) + $out .= ', '; + if($marr && in_arrayi($k,$marr)) + $out .= '' . $k . ''; + else + $out .= $k; + } + } + + } + + $entry = array( + 'id' => ++$t, + 'profile_link' => $profile_link, + 'public_forum' => $rr['public_forum'], + 'photo' => $rr['photo'], + 'hash' => $rr['hash'], + 'alttext' => $rr['name'] . ((local_channel() || remote_channel()) ? ' ' . $rr['address'] : ''), + 'name' => $rr['name'], + 'age' => $age, + 'age_label' => t('Age:'), + 'profile' => $profile, + 'address' => $rr['address'], + 'nickname' => substr($rr['address'],0,strpos($rr['address'],'@')), + 'location' => $location, + 'location_label' => t('Location:'), + 'gender' => $gender, + 'total_ratings' => $total_ratings, + 'viewrate' => true, + 'canrate' => ((local_channel()) ? true : false), + 'pdesc' => $pdesc, + 'pdesc_label' => t('Description:'), + 'marital' => $marital, + 'homepage' => $homepage, + 'homepageurl' => linkify($homepageurl), + 'hometown' => $hometown, + 'hometown_label' => t('Hometown:'), + 'about' => $about, + 'about_label' => t('About:'), + 'conn_label' => t('Connect'), + 'forum_label' => t('Public Forum:'), + 'connect' => $connect_link, + 'online' => $online, + 'kw' => (($out) ? t('Keywords: ') : ''), + 'keywords' => $out, + 'ignlink' => $suggest ? z_root() . '/directory?ignore=' . $rr['hash'] : '', + 'ignore_label' => t('Don\'t suggest'), + 'common_friends' => (($common[$rr['address']]) ? intval($common[$rr['address']]) : ''), + 'common_label' => t('Common connections:'), + 'common_count' => intval($common[$rr['address']]), + 'safe' => $safe_mode + ); + + $arr = array('contact' => $rr, 'entry' => $entry); + + call_hooks('directory_item', $arr); + + unset($profile); + unset($location); + + if(! $arr['entry']) { + continue; + } + + if($sort_order == '' && $suggest) { + $entries[$addresses[$rr['address']]] = $arr['entry']; // Use the same indexes as originally to get the best suggestion first + } + + else { + $entries[] = $arr['entry']; + } + } + + ksort($entries); // Sort array by key so that foreach-constructs work as expected + + if($j['keywords']) { + \App::$data['directory_keywords'] = $j['keywords']; + } + + logger('mod_directory: entries: ' . print_r($entries,true), LOGGER_DATA); + + + if($_REQUEST['aj']) { + if($entries) { + $o = replace_macros(get_markup_template('directajax.tpl'),array( + '$entries' => $entries + )); + } + else { + $o = '
'; + } + echo $o; + killme(); + } + else { + $maxheight = 94; + + $dirtitle = (($globaldir) ? t('Global Directory') : t('Local Directory')); + + $o .= ""; + $o .= replace_macros($tpl, array( + '$search' => $search, + '$desc' => t('Find'), + '$finddsc' => t('Finding:'), + '$safetxt' => htmlspecialchars($search,ENT_QUOTES,'UTF-8'), + '$entries' => $entries, + '$dirlbl' => $suggest ? t('Channel Suggestions') : $dirtitle, + '$submit' => t('Find'), + '$next' => alt_pager($a,$j['records'], t('next page'), t('previous page')), + '$sort' => t('Sort options'), + '$normal' => t('Alphabetic'), + '$reverse' => t('Reverse Alphabetic'), + '$date' => t('Newest to Oldest'), + '$reversedate' => t('Oldest to Newest'), + '$suggest' => $suggest ? '&suggest=1' : '' + )); + + + } + + } + else { + if($_REQUEST['aj']) { + $o = '
'; + echo $o; + killme(); + } + if(\App::$pager['page'] == 1 && $j['records'] == 0 && strpos($search,'@')) { + goaway(z_root() . '/chanview/?f=&address=' . $search); + } + info( t("No entries (some entries may be hidden).") . EOL); + } + } + } + } + return $o; + } + + +} diff --git a/Zotlabs/Module/Dirsearch.php b/Zotlabs/Module/Dirsearch.php new file mode 100644 index 000000000..8f60910f1 --- /dev/null +++ b/Zotlabs/Module/Dirsearch.php @@ -0,0 +1,462 @@ + false); + + // logger('request: ' . print_r($_REQUEST,true)); + + + $dirmode = intval(get_config('system','directory_mode')); + + if($dirmode == DIRECTORY_MODE_NORMAL) { + $ret['message'] = t('This site is not a directory server'); + json_return_and_die($ret); + } + + $access_token = $_REQUEST['t']; + + $token = get_config('system','realm_token'); + if($token && $access_token != $token) { + $ret['message'] = t('This directory server requires an access token'); + json_return_and_die($ret); + } + + + if(argc() > 1 && argv(1) === 'sites') { + $ret = $this->list_public_sites(); + json_return_and_die($ret); + } + + $sql_extra = ''; + + + $tables = array('name','address','locale','region','postcode','country','gender','marital','sexual','keywords'); + + if($_REQUEST['query']) { + $advanced = $this->dir_parse_query($_REQUEST['query']); + if($advanced) { + foreach($advanced as $adv) { + if(in_array($adv['field'],$tables)) { + if($adv['field'] === 'name') + $sql_extra .= $this->dir_query_build($adv['logic'],'xchan_name',$adv['value']); + elseif($adv['field'] === 'address') + $sql_extra .= $this->dir_query_build($adv['logic'],'xchan_addr',$adv['value']); + else + $sql_extra .= $this->dir_query_build($adv['logic'],'xprof_' . $adv['field'],$adv['value']); + } + } + } + } + + $hash = ((x($_REQUEST['hash'])) ? $_REQUEST['hash'] : ''); + + $name = ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''); + $hub = ((x($_REQUEST,'hub')) ? $_REQUEST['hub'] : ''); + $address = ((x($_REQUEST,'address')) ? $_REQUEST['address'] : ''); + $locale = ((x($_REQUEST,'locale')) ? $_REQUEST['locale'] : ''); + $region = ((x($_REQUEST,'region')) ? $_REQUEST['region'] : ''); + $postcode = ((x($_REQUEST,'postcode')) ? $_REQUEST['postcode'] : ''); + $country = ((x($_REQUEST,'country')) ? $_REQUEST['country'] : ''); + $gender = ((x($_REQUEST,'gender')) ? $_REQUEST['gender'] : ''); + $marital = ((x($_REQUEST,'marital')) ? $_REQUEST['marital'] : ''); + $sexual = ((x($_REQUEST,'sexual')) ? $_REQUEST['sexual'] : ''); + $keywords = ((x($_REQUEST,'keywords')) ? $_REQUEST['keywords'] : ''); + $agege = ((x($_REQUEST,'agege')) ? intval($_REQUEST['agege']) : 0 ); + $agele = ((x($_REQUEST,'agele')) ? intval($_REQUEST['agele']) : 0 ); + $kw = ((x($_REQUEST,'kw')) ? intval($_REQUEST['kw']) : 0 ); + $forums = ((array_key_exists('pubforums',$_REQUEST)) ? intval($_REQUEST['pubforums']) : 0); + + if(get_config('system','disable_directory_keywords')) + $kw = 0; + + + // by default use a safe search + $safe = ((x($_REQUEST,'safe'))); // ? intval($_REQUEST['safe']) : 1 ); + if ($safe === false) + $safe = 1; + + if(array_key_exists('sync',$_REQUEST)) { + if($_REQUEST['sync']) + $sync = datetime_convert('UTC','UTC',$_REQUEST['sync']); + else + $sync = datetime_convert('UTC','UTC','2010-01-01 01:01:00'); + } + else + $sync = false; + + + if($hub) + $hub_query = " and xchan_hash in (select hubloc_hash from hubloc where hubloc_host = '" . protect_sprintf(dbesc($hub)) . "') "; + else + $hub_query = ''; + + $sort_order = ((x($_REQUEST,'order')) ? $_REQUEST['order'] : ''); + + $joiner = ' OR '; + if($_REQUEST['and']) + $joiner = ' AND '; + + if($name) + $sql_extra .= $this->dir_query_build($joiner,'xchan_name',$name); + if($address) + $sql_extra .= $this->dir_query_build($joiner,'xchan_addr',$address); + if($city) + $sql_extra .= $this->dir_query_build($joiner,'xprof_locale',$city); + if($region) + $sql_extra .= $this->dir_query_build($joiner,'xprof_region',$region); + if($post) + $sql_extra .= $this->dir_query_build($joiner,'xprof_postcode',$post); + if($country) + $sql_extra .= $this->dir_query_build($joiner,'xprof_country',$country); + if($gender) + $sql_extra .= $this->dir_query_build($joiner,'xprof_gender',$gender); + if($marital) + $sql_extra .= $this->dir_query_build($joiner,'xprof_marital',$marital); + if($sexual) + $sql_extra .= $this->dir_query_build($joiner,'xprof_sexual',$sexual); + if($keywords) + $sql_extra .= $this->dir_query_build($joiner,'xprof_keywords',$keywords); + + + // we only support an age range currently. You must set both agege + // (greater than or equal) and agele (less than or equal) + + if($agele && $agege) { + $sql_extra .= " $joiner ( xprof_age <= " . intval($agele) . " "; + $sql_extra .= " AND xprof_age >= " . intval($agege) . ") "; + } + + + if($hash) { + $sql_extra = " AND xchan_hash like '" . dbesc($hash) . protect_sprintf('%') . "' "; + } + + + $perpage = (($_REQUEST['n']) ? $_REQUEST['n'] : 60); + $page = (($_REQUEST['p']) ? intval($_REQUEST['p'] - 1) : 0); + $startrec = (($page+1) * $perpage) - $perpage; + $limit = (($_REQUEST['limit']) ? intval($_REQUEST['limit']) : 0); + $return_total = ((x($_REQUEST,'return_total')) ? intval($_REQUEST['return_total']) : 0); + + // mtime is not currently working + + $mtime = ((x($_REQUEST,'mtime')) ? datetime_convert('UTC','UTC',$_REQUEST['mtime']) : ''); + + // ok a separate tag table won't work. + // merge them into xprof + + $ret['success'] = true; + + // If &limit=n, return at most n entries + // If &return_total=1, we count matching entries and return that as 'total_items' for use in pagination. + // By default we return one page (default 80 items maximum) and do not count total entries + + $logic = ((strlen($sql_extra)) ? 'false' : 'true'); + + if($hash) + $logic = 'true'; + + if($dirmode == DIRECTORY_MODE_STANDALONE) { + $sql_extra .= " and xchan_addr like '%%" . \App::get_hostname() . "' "; + } + + $safesql = (($safe > 0) ? " and xchan_censored = 0 and xchan_selfcensored = 0 " : ''); + if($safe < 0) + $safesql = " and ( xchan_censored = 1 OR xchan_selfcensored = 1 ) "; + + if($forums) + $safesql .= " and xchan_pubforum = " . ((intval($forums)) ? '1 ' : '0 '); + + if($limit) + $qlimit = " LIMIT $limit "; + else { + $qlimit = " LIMIT " . intval($perpage) . " OFFSET " . intval($startrec); + if($return_total) { + $r = q("SELECT COUNT(xchan_hash) AS `total` FROM xchan left join xprof on xchan_hash = xprof_hash where $logic $sql_extra and xchan_network = 'zot' and xchan_hidden = 0 and xchan_orphan = 0 and xchan_deleted = 0 $safesql "); + if($r) { + $ret['total_items'] = $r[0]['total']; + } + } + } + + if($sort_order == 'normal') { + $order = " order by xchan_name asc "; + + // Start the alphabetic search at 'A' + // This will make a handful of channels whose names begin with + // punctuation un-searchable in this mode + + $safesql .= " and ascii(substring(xchan_name FROM 1 FOR 1)) > 64 "; + } + elseif($sort_order == 'reverse') + $order = " order by xchan_name desc "; + elseif($sort_order == 'reversedate') + $order = " order by xchan_name_date asc "; + else + $order = " order by xchan_name_date desc "; + + + if($sync) { + $spkt = array('transactions' => array()); + $r = q("select * from updates where ud_date >= '%s' and ud_guid != '' order by ud_date desc", + dbesc($sync) + ); + if($r) { + foreach($r as $rr) { + $flags = array(); + if($rr['ud_flags'] & UPDATE_FLAGS_DELETED) + $flags[] = 'deleted'; + if($rr['ud_flags'] & UPDATE_FLAGS_FORCED) + $flags[] = 'forced'; + + $spkt['transactions'][] = array( + 'hash' => $rr['ud_hash'], + 'address' => $rr['ud_addr'], + 'transaction_id' => $rr['ud_guid'], + 'timestamp' => $rr['ud_date'], + 'flags' => $flags + ); + } + } + $r = q("select * from xlink where xlink_static = 1 and xlink_updated >= '%s' ", + dbesc($sync) + ); + if($r) { + $spkt['ratings'] = array(); + foreach($r as $rr) { + $spkt['ratings'][] = array( + 'type' => 'rating', + 'encoding' => 'zot', + 'channel' => $rr['xlink_xchan'], + 'target' => $rr['xlink_link'], + 'rating' => intval($rr['xlink_rating']), + 'rating_text' => $rr['xlink_rating_text'], + 'signature' => $rr['xlink_sig'], + 'edited' => $rr['xlink_updated'] + ); + } + } + json_return_and_die($spkt); + } + else { + + $r = q("SELECT xchan.*, xprof.* from xchan left join xprof on xchan_hash = xprof_hash + where ( $logic $sql_extra ) $hub_query and xchan_network = 'zot' and xchan_hidden = 0 and xchan_orphan = 0 and xchan_deleted = 0 + $safesql $order $qlimit " + ); + + + + $ret['page'] = $page + 1; + $ret['records'] = count($r); + } + + + + if($r) { + + $entries = array(); + + foreach($r as $rr) { + + $entry = array(); + + $pc = q("select count(xlink_rating) as total_ratings from xlink where xlink_link = '%s' and xlink_rating != 0 and xlink_static = 1 group by xlink_rating", + dbesc($rr['xchan_hash']) + ); + + if($pc) + $entry['total_ratings'] = intval($pc[0]['total_ratings']); + else + $entry['total_ratings'] = 0; + + $entry['name'] = $rr['xchan_name']; + $entry['hash'] = $rr['xchan_hash']; + + $entry['public_forum'] = (intval($rr['xchan_pubforum']) ? true : false); + + $entry['url'] = $rr['xchan_url']; + $entry['photo_l'] = $rr['xchan_photo_l']; + $entry['photo'] = $rr['xchan_photo_m']; + $entry['address'] = $rr['xchan_addr']; + $entry['description'] = $rr['xprof_desc']; + $entry['locale'] = $rr['xprof_locale']; + $entry['region'] = $rr['xprof_region']; + $entry['postcode'] = $rr['xprof_postcode']; + $entry['country'] = $rr['xprof_country']; + $entry['birthday'] = $rr['xprof_dob']; + $entry['age'] = $rr['xprof_age']; + $entry['gender'] = $rr['xprof_gender']; + $entry['marital'] = $rr['xprof_marital']; + $entry['sexual'] = $rr['xprof_sexual']; + $entry['about'] = $rr['xprof_about']; + $entry['homepage'] = $rr['xprof_homepage']; + $entry['hometown'] = $rr['xprof_hometown']; + $entry['keywords'] = $rr['xprof_keywords']; + + $entries[] = $entry; + + } + + $ret['results'] = $entries; + if($kw) { + $k = dir_tagadelic($kw); + if($k) { + $ret['keywords'] = array(); + foreach($k as $kv) { + $ret['keywords'][] = array('term' => $kv[0],'weight' => $kv[1], 'normalise' => $kv[2]); + } + } + } + } + + json_return_and_die($ret); + } + + function dir_query_build($joiner,$field,$s) { + $ret = ''; + if(trim($s)) + $ret .= dbesc($joiner) . " " . dbesc($field) . " like '" . protect_sprintf( '%' . dbesc($s) . '%' ) . "' "; + return $ret; + } + + function dir_flag_build($joiner,$field,$bit,$s) { + return dbesc($joiner) . " ( " . dbesc($field) . " & " . intval($bit) . " ) " . ((intval($s)) ? '>' : '=' ) . " 0 "; + } + + + function dir_parse_query($s) { + + $ret = array(); + $curr = array(); + $all = explode(' ',$s); + $quoted_string = false; + + if($all) { + foreach($all as $q) { + if($quoted_string === false) { + if($q === 'and') { + $curr['logic'] = 'and'; + continue; + } + if($q === 'or') { + $curr['logic'] = 'or'; + continue; + } + if($q === 'not') { + $curr['logic'] .= ' not'; + continue; + } + if(strpos($q,'=')) { + if(! isset($curr['logic'])) + $curr['logic'] = 'or'; + $curr['field'] = trim(substr($q,0,strpos($q,'='))); + $curr['value'] = trim(substr($q,strpos($q,'=')+1)); + if($curr['value'][0] == '"' && $curr['value'][strlen($curr['value'])-1] != '"') { + $quoted_string = true; + $curr['value'] = substr($curr['value'],1); + continue; + } + elseif($curr['value'][0] == '"' && $curr['value'][strlen($curr['value'])-1] == '"') { + $curr['value'] = substr($curr['value'],1,strlen($curr['value'])-2); + $ret[] = $curr; + $curr = array(); + continue; + } + else { + $ret[] = $curr; + $curr = array(); + continue; + } + } + } + else { + if($q[strlen($q)-1] == '"') { + $curr['value'] .= ' ' . str_replace('"','',trim($q)); + $ret[] = $curr; + $curr = array(); + $quoted_string = false; + } + else + $curr['value'] .= ' ' . trim(q); + } + } + } + logger('dir_parse_query:' . print_r($ret,true),LOGGER_DATA); + return $ret; + } + + + + + + + + function list_public_sites() { + + $rand = db_getfunc('rand'); + $realm = get_directory_realm(); + if($realm == DIRECTORY_REALM) { + $r = q("select * from site where site_access != 0 and site_register !=0 and ( site_realm = '%s' or site_realm = '') and site_type = %d order by $rand", + dbesc($realm), + intval(SITE_TYPE_ZOT) + ); + } + else { + $r = q("select * from site where site_access != 0 and site_register !=0 and site_realm = '%s' and site_type = %d order by $rand", + dbesc($realm), + intval(SITE_TYPE_ZOT) + ); + } + + $ret = array('success' => false); + + if($r) { + $ret['success'] = true; + $ret['sites'] = array(); + $insecure = array(); + + foreach($r as $rr) { + + if($rr['site_access'] == ACCESS_FREE) + $access = 'free'; + elseif($rr['site_access'] == ACCESS_PAID) + $access = 'paid'; + elseif($rr['site_access'] == ACCESS_TIERED) + $access = 'tiered'; + else + $access = 'private'; + + if($rr['site_register'] == REGISTER_OPEN) + $register = 'open'; + elseif($rr['site_register'] == REGISTER_APPROVE) + $register = 'approve'; + else + $register = 'closed'; + + if(strpos($rr['site_url'],'https://') !== false) + $ret['sites'][] = array('url' => $rr['site_url'], 'access' => $access, 'register' => $register, 'sellpage' => $rr['site_sellpage'], 'location' => $rr['site_location'], 'project' => $rr['site_project']); + else + $insecure[] = array('url' => $rr['site_url'], 'access' => $access, 'register' => $register, 'sellpage' => $rr['site_sellpage'], 'location' => $rr['site_location'], 'project' => $rr['site_project']); + } + if($insecure) { + $ret['sites'] = array_merge($ret['sites'],$insecure); + } + } + return $ret; + } + +} diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php new file mode 100644 index 000000000..d1d4edc7d --- /dev/null +++ b/Zotlabs/Module/Display.php @@ -0,0 +1,343 @@ + 1 && argv(1) !== 'load') + $item_hash = argv(1); + + + if($_REQUEST['mid']) + $item_hash = $_REQUEST['mid']; + + + if(! $item_hash) { + \App::$error = 404; + notice( t('Item not found.') . EOL); + return; + } + + $observer_is_owner = false; + + + if(local_channel() && (! $update)) { + + $channel = \App::get_channel(); + + + $channel_acl = array( + 'allow_cid' => $channel['channel_allow_cid'], + 'allow_gid' => $channel['channel_allow_gid'], + 'deny_cid' => $channel['channel_deny_cid'], + 'deny_gid' => $channel['channel_deny_gid'] + ); + + + $x = array( + 'is_owner' => true, + 'allow_location' => ((intval(get_pconfig($channel['channel_id'],'system','use_browser_location'))) ? '1' : ''), + 'default_location' => $channel['channel_location'], + 'nickname' => $channel['channel_address'], + 'lockstate' => (($group || $cid || $channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), + + 'acl' => populate_acl($channel_acl), + 'bang' => '', + 'visitor' => true, + 'profile_uid' => local_channel(), + 'return_path' => 'channel/' . $channel['channel_address'], + 'expanded' => true, + 'editor_autocomplete' => true, + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ); + + $o = '
'; + $o .= status_editor($a,$x); + $o .= '
'; + + } + + // This page can be viewed by anybody so the query could be complicated + // First we'll see if there is a copy of the item which is owned by us - if we're logged in locally. + // If that fails (or we aren't logged in locally), + // query an item in which the observer (if logged in remotely) has cid or gid rights + // and if that fails, look for a copy of the post that has no privacy restrictions. + // If we find the post, but we don't find a copy that we're allowed to look at, this fact needs to be reported. + + // find a copy of the item somewhere + + $target_item = null; + + $r = q("select id, uid, mid, parent_mid, item_type, item_deleted from item where mid like '%s' limit 1", + dbesc($item_hash . '%') + ); + + if($r) { + $target_item = $r[0]; + } + + $r = null; + + if($target_item['item_type'] == ITEM_TYPE_WEBPAGE) { + $x = q("select * from channel where channel_id = %d limit 1", + intval($target_item['uid']) + ); + $y = q("select * from iconfig left join item on iconfig.iid = item.id + where item.uid = %d and iconfig.cat = 'system' and iconfig.k = 'WEBPAGE' and item.id = %d limit 1", + intval($target_item['uid']), + intval($target_item['id']) + ); + if($x && $y) { + goaway(z_root() . '/page/' . $x[0]['channel_address'] . '/' . $y[0]['v']); + } + else { + notice( t('Page not found.') . EOL); + return ''; + } + } + + + $simple_update = (($update) ? " AND item_unseen = 1 " : ''); + + if($update && $_SESSION['loadtime']) + $simple_update = " AND (( item_unseen = 1 AND item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) OR item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) "; + if($load) + $simple_update = ''; + + + + if((! $update) && (! $load)) { + + + $o .= '
' . "\r\n"; + $o .= "\r\n"; + + \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + '$baseurl' => z_root(), + '$pgtype' => 'display', + '$uid' => '0', + '$gid' => '0', + '$cid' => '0', + '$cmin' => '0', + '$cmax' => '99', + '$star' => '0', + '$liked' => '0', + '$conv' => '0', + '$spam' => '0', + '$fh' => '0', + '$nouveau' => '0', + '$wall' => '0', + '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0), + '$search' => '', + '$order' => '', + '$file' => '', + '$cats' => '', + '$tags' => '', + '$dend' => '', + '$dbegin' => '', + '$verb' => '', + '$mid' => $item_hash + )); + + + } + + $observer_hash = get_observer_hash(); + $item_normal = item_normal(); + + $sql_extra = public_permissions_sql($observer_hash); + + if(($update && $load) || ($checkjs->disabled())) { + + $updateable = false; + + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']),intval(\App::$pager['start'])); + + if($load || ($checkjs->disabled())) { + $r = null; + + require_once('include/channel.php'); + $sys = get_sys_channel(); + $sysid = $sys['channel_id']; + + if(local_channel()) { + $r = q("SELECT * from item + WHERE uid = %d + and mid = '%s' + $item_normal + limit 1", + intval(local_channel()), + dbesc($target_item['parent_mid']) + ); + if($r) { + $updateable = true; + + } + + } + if($r === null) { + + // in case somebody turned off public access to sys channel content using permissions + // make that content unsearchable by ensuring the owner_xchan can't match + + if(! perm_is_allowed($sysid,$observer_hash,'view_stream')) + $sysid = 0; + + + $r = q("SELECT * from item + WHERE mid = '%s' + AND (((( `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' + AND `item`.`deny_gid` = '' AND item_private = 0 ) + and owner_xchan in ( " . stream_perms_xchans(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) + OR uid = %d ) + $sql_extra ) + $item_normal + limit 1", + dbesc($target_item['parent_mid']), + intval($sysid) + ); + + } + } + } + + elseif($update && !$load) { + $r = null; + + require_once('include/channel.php'); + $sys = get_sys_channel(); + $sysid = $sys['channel_id']; + + if(local_channel()) { + $r = q("SELECT * from item + WHERE uid = %d + and mid = '%s' + $item_normal + $simple_update + limit 1", + intval(local_channel()), + dbesc($target_item['parent_mid']) + ); + if($r) { + $updateable = true; + } + } + if($r === null) { + // in case somebody turned off public access to sys channel content using permissions + // make that content unsearchable by ensuring the owner_xchan can't match + if(! perm_is_allowed($sysid,$observer_hash,'view_stream')) + $sysid = 0; + + $r = q("SELECT * from item + WHERE mid = '%s' + AND (((( `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' + AND `item`.`deny_gid` = '' AND item_private = 0 ) + and owner_xchan in ( " . stream_perms_xchans(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) + OR uid = %d ) + $sql_extra ) + $item_normal + $simple_update + limit 1", + dbesc($target_item['parent_mid']), + intval($sysid) + ); + } + $_SESSION['loadtime'] = datetime_convert(); + } + + else { + $r = array(); + } + + if($r) { + + $parents_str = ids_to_querystr($r,'id'); + if($parents_str) { + + $items = q("SELECT `item`.*, `item`.`id` AS `item_id` + FROM `item` + WHERE parent in ( %s ) $item_normal ", + dbesc($parents_str) + ); + + xchan_query($items); + $items = fetch_post_tags($items,true); + $items = conv_sort($items,'created'); + } + } else { + $items = array(); + } + + + if ($checkjs->disabled()) { + $o .= conversation($a, $items, 'display', $update, 'traditional'); + if ($items[0]['title']) + \App::$page['title'] = $items[0]['title'] . " - " . \App::$page['title']; + } + else { + $o .= conversation($a, $items, 'display', $update, 'client'); + } + + if($updateable) { + $x = q("UPDATE item SET item_unseen = 0 where item_unseen = 1 AND uid = %d and parent = %d ", + intval(local_channel()), + intval($r[0]['parent']) + ); + } + + $o .= '
'; + + return $o; + + + /* + elseif((! $update) && (! { + + $r = q("SELECT `id`, item_flags FROM `item` WHERE `id` = '%s' OR `mid` = '%s' LIMIT 1", + dbesc($item_hash), + dbesc($item_hash) + ); + if($r) { + if(intval($r[0]['item_deleted'])) { + notice( t('Item has been removed.') . EOL ); + } + else { + notice( t('Permission denied.') . EOL ); + } + } + else { + notice( t('Item not found.') . EOL ); + } + + } + */ + } + + +} diff --git a/Zotlabs/Module/Dreport.php b/Zotlabs/Module/Dreport.php new file mode 100644 index 000000000..d2933b464 --- /dev/null +++ b/Zotlabs/Module/Dreport.php @@ -0,0 +1,170 @@ + 1) ? argv(1) : ''); + + if($mid === 'push') { + $table = 'push'; + $mid = ((argc() > 2) ? argv(2) : ''); + if($mid) { + $i = q("select id from item where mid = '%s' and author_xchan = '%s' and uid = %d", + dbesc($mid), + dbesc($channel['channel_hash']), + intval($channel['channel_id']) + ); + if($i) { + \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'edit_post', $i[0]['id'] ]); + } + } + sleep(3); + goaway(z_root() . '/dreport/' . urlencode($mid)); + } + + if($mid === 'mail') { + $table = 'mail'; + $mid = ((argc() > 2) ? argv(2) : ''); + } + + + if(! $mid) { + notice( t('Invalid message') . EOL); + return; + } + + switch($table) { + case 'item': + $i = q("select id from item where mid = '%s' and author_xchan = '%s' ", + dbesc($mid), + dbesc($channel['channel_hash']) + ); + break; + case 'mail': + $i = q("select id from mail where mid = '%s' and from_xchan = '%s'", + dbesc($mid), + dbesc($channel['channel_hash']) + ); + break; + default: + break; + } + + if(! $i) { + notice( t('Permission denied') . EOL); + return; + } + + $r = q("select * from dreport where dreport_xchan = '%s' and dreport_mid = '%s'", + dbesc($channel['channel_hash']), + dbesc($mid) + ); + + if(! $r) { + notice( t('no results') . EOL); + return; + } + + for($x = 0; $x < count($r); $x++ ) { + $r[$x]['name'] = escape_tags(substr($r[$x]['dreport_recip'],strpos($r[$x]['dreport_recip'],' '))); + + // This has two purposes: 1. make the delivery report strings translateable, and + // 2. assign an ordering to item delivery results so we can group them and provide + // a readable report with more interesting events listed toward the top and lesser + // interesting items towards the bottom + + switch($r[$x]['dreport_result']) { + case 'channel sync processed': + $r[$x]['gravity'] = 0; + $r[$x]['dreport_result'] = t('channel sync processed'); + break; + case 'queued': + $r[$x]['gravity'] = 2; + $r[$x]['dreport_result'] = t('queued'); + break; + case 'posted': + $r[$x]['gravity'] = 3; + $r[$x]['dreport_result'] = t('posted'); + break; + case 'accepted for delivery': + $r[$x]['gravity'] = 4; + $r[$x]['dreport_result'] = t('accepted for delivery'); + break; + case 'updated': + $r[$x]['gravity'] = 5; + $r[$x]['dreport_result'] = t('updated'); + case 'update ignored': + $r[$x]['gravity'] = 6; + $r[$x]['dreport_result'] = t('update ignored'); + break; + case 'permission denied': + $r[$x]['dreport_result'] = t('permission denied'); + $r[$x]['gravity'] = 6; + break; + case 'recipient not found': + $r[$x]['dreport_result'] = t('recipient not found'); + break; + case 'mail recalled': + $r[$x]['dreport_result'] = t('mail recalled'); + break; + case 'duplicate mail received': + $r[$x]['dreport_result'] = t('duplicate mail received'); + break; + case 'mail delivered': + $r[$x]['dreport_result'] = t('mail delivered'); + break; + default: + $r[$x]['gravity'] = 1; + break; + } + } + + usort($r,'self::dreport_gravity_sort'); + + $entries = array(); + foreach($r as $rr) { + $entries[] = [ + 'name' => $rr['name'], + 'result' => escape_tags($rr['dreport_result']), + 'time' => escape_tags(datetime_convert('UTC',date_default_timezone_get(),$rr['dreport_time'])) + ]; + } + + $o = replace_macros(get_markup_template('dreport.tpl'), array( + '$title' => sprintf( t('Delivery report for %1$s'),substr($mid,0,32)) . '...', + '$table' => $table, + '$mid' => urlencode($mid), + '$options' => t('Options'), + '$push' => t('Redeliver'), + '$entries' => $entries + )); + + + return $o; + + + + } + + private static function dreport_gravity_sort($a,$b) { + if($a['gravity'] == $b['gravity']) { + if($a['name'] === $b['name']) + return strcmp($a['dreport_time'],$b['dreport_time']); + return strcmp($a['name'],$b['name']); + } + return (($a['gravity'] > $b['gravity']) ? 1 : (-1)); + } + +} diff --git a/Zotlabs/Module/Editblock.php b/Zotlabs/Module/Editblock.php new file mode 100644 index 000000000..6a9fa5f2d --- /dev/null +++ b/Zotlabs/Module/Editblock.php @@ -0,0 +1,143 @@ + 1 && argv(1) === 'sys' && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + \App::$is_sys = true; + } + } + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + function get() { + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $which = argv(1); + + $uid = local_channel(); + $owner = 0; + $channel = null; + $observer = \App::get_observer(); + + $channel = \App::get_channel(); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + $uid = $owner = intval($sys['channel_id']); + $channel = $sys; + $observer = $sys; + } + } + + if(! $owner) { + // Figure out who the page owner is. + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($owner,$ob_hash,'write_pages')) { + notice( t('Permission denied.') . EOL); + return; + } + + $is_owner = (($uid && $uid == $owner) ? true : false); + + $o = ''; + + // Figure out which post we're editing + $post_id = ((argc() > 2) ? intval(argv(2)) : 0); + + if(! ($post_id && $owner)) { + notice( t('Item not found') . EOL); + return; + } + + $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s LIMIT 1", + intval($post_id), + intval($owner) + ); + if($itm) { + $item_id = q("select * from iconfig where cat = 'system' and k = 'BUILDBLOCK' and iid = %d limit 1", + intval($itm[0]['id']) + ); + if($item_id) + $block_title = $item_id[0]['v']; + } + else { + notice( t('Item not found') . EOL); + return; + } + + $mimetype = $itm[0]['mimetype']; + + $rp = 'blocks/' . $channel['channel_address']; + + $x = array( + 'nickname' => $channel['channel_address'], + 'bbco_autocomplete'=> (($mimetype == 'text/bbcode') ? 'bbcode' : 'comanche-block'), + 'return_path' => $rp, + 'webpage' => ITEM_TYPE_BLOCK, + 'ptlabel' => t('Block Name'), + 'button' => t('Edit'), + 'writefiles' => (($mimetype == 'text/bbcode') ? perm_is_allowed($owner, get_observer_hash(), 'write_storage') : false), + 'weblink' => (($mimetype == 'text/bbcode') ? t('Insert web link') : false), + 'hide_voting' => true, + 'hide_future' => true, + 'hide_location' => true, + 'hide_expire' => true, + 'showacl' => false, + 'ptyp' => $itm[0]['type'], + 'mimeselect' => true, + 'mimetype' => $itm[0]['mimetype'], + 'body' => undo_post_tagging($itm[0]['body']), + 'post_id' => $post_id, + 'visitor' => true, + 'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'), + 'placeholdertitle' => t('Title (optional)'), + 'pagetitle' => $block_title, + 'profile_uid' => (intval($channel['channel_id'])), + 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) + ); + + $editor = status_editor($a, $x); + + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( + '$title' => t('Edit Block'), + '$delete' => ((($itm[0]['author_xchan'] === $ob_hash) || ($itm[0]['owner_xchan'] === $ob_hash)) ? t('Delete') : false), + '$id' => $itm[0]['id'], + '$editor' => $editor + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Editlayout.php b/Zotlabs/Module/Editlayout.php new file mode 100644 index 000000000..26732dc77 --- /dev/null +++ b/Zotlabs/Module/Editlayout.php @@ -0,0 +1,146 @@ + 1 && argv(1) === 'sys' && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + \App::$is_sys = true; + } + } + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + function get() { + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $which = argv(1); + + $uid = local_channel(); + $owner = 0; + $channel = null; + $observer = \App::get_observer(); + + $channel = \App::get_channel(); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + $uid = $owner = intval($sys['channel_id']); + $channel = $sys; + $observer = $sys; + } + } + + if(! $owner) { + // Figure out who the page owner is. + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($owner,$ob_hash,'write_pages')) { + notice( t('Permission denied.') . EOL); + return; + } + + $is_owner = (($uid && $uid == $owner) ? true : false); + + $o = ''; + + // Figure out which post we're editing + $post_id = ((argc() > 2) ? intval(argv(2)) : 0); + + if(! $post_id) { + notice( t('Item not found') . EOL); + return; + } + + // Now we've got a post and an owner, let's find out if we're allowed to edit it + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms($owner,$ob_hash); + + if(! $perms['write_pages']) { + notice( t('Permission denied.') . EOL); + return; + } + + $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s LIMIT 1", + intval($post_id), + intval($owner) + ); + + $item_id = q("select * from iconfig where cat = 'system' and k = 'PDL' and iid = %d limit 1", + intval($itm[0]['id']) + ); + if($item_id) + $layout_title = $item_id[0]['v']; + + + $rp = 'layouts/' . $which; + + $x = array( + 'webpage' => ITEM_TYPE_PDL, + 'nickname' => $channel['channel_address'], + 'editor_autocomplete'=> true, + 'bbco_autocomplete'=> 'comanche', + 'return_path' => $rp, + 'button' => t('Edit'), + 'hide_voting' => true, + 'hide_future' => true, + 'hide_expire' => true, + 'hide_location' => true, + 'hide_weblink' => true, + 'hide_attach' => true, + 'hide_preview' => true, + 'ptyp' => $itm[0]['obj_type'], + 'body' => undo_post_tagging($itm[0]['body']), + 'post_id' => $post_id, + 'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'), + 'pagetitle' => $layout_title, + 'ptlabel' => t('Layout Name'), + 'placeholdertitle' => t('Layout Description (Optional)'), + 'showacl' => false, + 'profile_uid' => intval($owner), + ); + + $editor = status_editor($a, $x); + + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( + '$title' => t('Edit Layout'), + '$delete' => ((($itm[0]['author_xchan'] === $ob_hash) || ($itm[0]['owner_xchan'] === $ob_hash)) ? t('Delete') : false), + '$id' => $itm[0]['id'], + '$editor' => $editor + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Editpost.php b/Zotlabs/Module/Editpost.php new file mode 100644 index 000000000..838fe9e4f --- /dev/null +++ b/Zotlabs/Module/Editpost.php @@ -0,0 +1,114 @@ + 1) ? intval(argv(1)) : 0); + + if(! $post_id) { + notice( t('Item not found') . EOL); + return; + } + + $itm = q("SELECT * FROM `item` WHERE `id` = %d AND ( owner_xchan = '%s' OR author_xchan = '%s' ) LIMIT 1", + intval($post_id), + dbesc(get_observer_hash()), + dbesc(get_observer_hash()) + ); + + if(! count($itm)) { + notice( t('Item is not editable') . EOL); + return; + } + + if($itm[0]['resource_type'] === 'event' && $itm[0]['resource_id']) { + goaway(z_root() . '/events/' . $itm[0]['resource_id'] . '?expandform=1'); + } + + $owner_uid = $itm[0]['uid']; + + $channel = \App::get_channel(); + + if(intval($itm[0]['item_obscured'])) { + $key = get_config('system','prvkey'); + if($itm[0]['title']) + $itm[0]['title'] = crypto_unencapsulate(json_decode($itm[0]['title'],true),$key); + if($itm[0]['body']) + $itm[0]['body'] = crypto_unencapsulate(json_decode($itm[0]['body'],true),$key); + } + + $category = ''; + $catsenabled = ((feature_enabled($owner_uid,'categories')) ? 'categories' : ''); + + if ($catsenabled){ + $itm = fetch_post_tags($itm); + + $cats = get_terms_oftype($itm[0]['term'], TERM_CATEGORY); + + foreach ($cats as $cat) { + if (strlen($category)) + $category .= ', '; + $category .= $cat['term']; + } + } + + if($itm[0]['attach']) { + $j = json_decode($itm[0]['attach'],true); + if($j) { + foreach($j as $jj) { + $itm[0]['body'] .= "\n" . '[attachment]' . basename($jj['href']) . ',' . $jj['revision'] . '[/attachment]' . "\n"; + } + } + } + + $x = array( + 'nickname' => $channel['channel_address'], + 'editor_autocomplete'=> true, + 'bbco_autocomplete'=> 'bbcode', + 'return_path' => $_SESSION['return_url'], + 'button' => t('Edit'), + 'hide_voting' => true, + 'hide_future' => true, + 'hide_location' => true, + 'mimetype' => $itm[0]['mimetype'], + 'ptyp' => $itm[0]['obj_type'], + 'body' => htmlspecialchars_decode(undo_post_tagging($itm[0]['body']),ENT_COMPAT), + 'post_id' => $post_id, + 'defloc' => $channel['channel_location'], + 'visitor' => true, + 'title' => htmlspecialchars_decode($itm[0]['title'],ENT_COMPAT), + 'category' => $category, + 'showacl' => false, + 'profile_uid' => $owner_uid, + 'catsenabled' => $catsenabled, + 'hide_expire' => true, + 'bbcode' => true + ); + + $editor = status_editor($a, $x); + + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( + '$title' => t('Edit post'), + '$editor' => $editor + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Editwebpage.php b/Zotlabs/Module/Editwebpage.php new file mode 100644 index 000000000..a55f81101 --- /dev/null +++ b/Zotlabs/Module/Editwebpage.php @@ -0,0 +1,178 @@ + 1 && argv(1) === 'sys' && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + \App::$is_sys = true; + } + } + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + function get() { + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $which = argv(1); + + $uid = local_channel(); + $owner = 0; + $channel = null; + $observer = \App::get_observer(); + + $channel = \App::get_channel(); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + $uid = $owner = intval($sys['channel_id']); + $channel = $sys; + $observer = $sys; + } + } + + if(! $owner) { + // Figure out who the page owner is. + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($owner,$ob_hash,'write_pages')) { + notice( t('Permission denied.') . EOL); + return; + } + + $is_owner = (($uid && $uid == $owner) ? true : false); + + $o = ''; + + // Figure out which post we're editing + $post_id = ((argc() > 2) ? intval(argv(2)) : 0); + + if(! $post_id) { + notice( t('Item not found') . EOL); + return; + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms($owner,$ob_hash); + + if(! $perms['write_pages']) { + notice( t('Permission denied.') . EOL); + return; + } + + // We've already figured out which item we want and whose copy we need, + // so we don't need anything fancy here + + $sql_extra = item_permissions_sql($owner); + + $itm = q("SELECT * FROM `item` WHERE `id` = %d and uid = %s $sql_extra LIMIT 1", + intval($post_id), + intval($owner) + ); + + if(! $itm) { + notice( t('Permission denied.') . EOL); + return; + } + + if(intval($itm[0]['item_obscured'])) { + $key = get_config('system','prvkey'); + if($itm[0]['title']) + $itm[0]['title'] = crypto_unencapsulate(json_decode($itm[0]['title'],true),$key); + if($itm[0]['body']) + $itm[0]['body'] = crypto_unencapsulate(json_decode($itm[0]['body'],true),$key); + } + + $item_id = q("select * from iconfig where cat = 'system' and k = 'WEBPAGE' and iid = %d limit 1", + intval($itm[0]['id']) + ); + if($item_id) + $page_title = $item_id[0]['v']; + + $mimetype = $itm[0]['mimetype']; + + if($mimetype === 'application/x-php') { + if((! $uid) || ($uid != $itm[0]['uid'])) { + notice( t('Permission denied.') . EOL); + return; + } + } + + $layout = $itm[0]['layout_mid']; + + $tpl = get_markup_template("jot.tpl"); + + $rp = 'webpages/' . $which; + + $x = array( + 'nickname' => $channel['channel_address'], + 'bbco_autocomplete'=> (($mimetype == 'text/bbcode') ? 'bbcode' : ''), + 'return_path' => $rp, + 'webpage' => ITEM_TYPE_WEBPAGE, + 'ptlabel' => t('Page link'), + 'pagetitle' => $page_title, + 'writefiles' => (($mimetype == 'text/bbcode') ? perm_is_allowed($owner, get_observer_hash(), 'write_storage') : false), + 'button' => t('Edit'), + 'weblink' => (($mimetype == 'text/bbcode') ? t('Insert web link') : false), + 'hide_location' => true, + 'hide_voting' => true, + 'ptyp' => $itm[0]['type'], + 'body' => undo_post_tagging($itm[0]['body']), + 'post_id' => $post_id, + 'visitor' => ($is_owner) ? true : false, + 'acl' => populate_acl($itm[0],false,\Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')), + 'showacl' => ($is_owner) ? true : false, + 'mimetype' => $mimetype, + 'mimeselect' => true, + 'layout' => $layout, + 'layoutselect' => true, + 'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'), + 'lockstate' => (((strlen($itm[0]['allow_cid'])) || (strlen($itm[0]['allow_gid'])) || (strlen($itm[0]['deny_cid'])) || (strlen($itm[0]['deny_gid']))) ? 'lock' : 'unlock'), + 'profile_uid' => (intval($owner)), + 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) + ); + + $editor = status_editor($a, $x); + + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( + '$title' => t('Edit Webpage'), + '$delete' => ((($itm[0]['author_xchan'] === $ob_hash) || ($itm[0]['owner_xchan'] === $ob_hash)) ? t('Delete') : false), + '$editor' => $editor, + '$id' => $itm[0]['id'] + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Embedphotos.php b/Zotlabs/Module/Embedphotos.php new file mode 100644 index 000000000..0dac873c5 --- /dev/null +++ b/Zotlabs/Module/Embedphotos.php @@ -0,0 +1,180 @@ + 1 && argv(1) === 'album') { + // API: /embedphotos/album + $name = (x($_POST,'name') ? $_POST['name'] : null ); + if (!$name) { + json_return_and_die(array('errormsg' => 'Error retrieving album', 'status' => false)); + } + $album = $this->embedphotos_widget_album(array('channel' => \App::get_channel(), 'album' => $name)); + json_return_and_die(array('status' => true, 'content' => $album)); + + } + if (argc() > 1 && argv(1) === 'albumlist') { + // API: /embedphotos/albumlist + $album_list = $this->embedphotos_album_list($a); + json_return_and_die(array('status' => true, 'albumlist' => $album_list)); + + } + if (argc() > 1 && argv(1) === 'photolink') { + // API: /embedphotos/photolink + $href = (x($_POST,'href') ? $_POST['href'] : null ); + if (!$href) { + json_return_and_die(array('errormsg' => 'Error retrieving link ' . $href, 'status' => false)); + } + $resource_id = array_pop(explode("/", $href)); + $r = q("SELECT obj from item where resource_type = 'photo' and resource_id = '%s' limit 1", + dbesc($resource_id) + ); + if(!$r) { + json_return_and_die(array('errormsg' => 'Error retrieving resource ' . $resource_id, 'status' => false)); + } + $obj = json_decode($r[0]['obj'], true); + if(x($obj,'body')) { + $photolink = $obj['body']; + } elseif (x($obj,'bbcode')) { + $photolink = $obj['bbcode']; + } else { + json_return_and_die(array('errormsg' => 'Error retrieving resource ' . $resource_id, 'status' => false)); + } + json_return_and_die(array('status' => true, 'photolink' => $photolink)); + + } + } + + +/** + * Copied from include/widgets.php::widget_album() with a modification to get the profile_uid from + * the input array as in widget_item() + * @param type $name + * @return string + */ +function embedphotos_widget_album($args) { + + $channel_id = 0; + if(array_key_exists('channel',$args)) + $channel = $args['channel']; + $channel_id = intval($channel['channel_id']); + if(! $channel_id) + $channel_id = \App::$profile_uid; + if(! $channel_id) + return ''; + $owner_uid = $channel_id; + require_once('include/security.php'); + $sql_extra = permissions_sql($channel_id); + + if(! perm_is_allowed($channel_id,get_observer_hash(),'view_storage')) + return ''; + + if($args['album']) + $album = $args['album']; + if($args['title']) + $title = $args['title']; + + /** + * This may return incorrect permissions if you have multiple directories of the same name. + * It is a limitation of the photo table using a name for a photo album instead of a folder hash + */ + + if($album) { + $x = q("select hash from attach where filename = '%s' and uid = %d limit 1", + dbesc($album), + intval($owner_uid) + ); + if($x) { + $y = attach_can_view_folder($owner_uid,get_observer_hash(),$x[0]['hash']); + if(! $y) + return ''; + } + } + + $order = 'DESC'; + + $r = q("SELECT p.resource_id, p.id, p.filename, p.mimetype, p.imgscale, p.description, p.created FROM photo p INNER JOIN + (SELECT resource_id, max(imgscale) imgscale FROM photo WHERE uid = %d AND album = '%s' AND imgscale <= 4 AND photo_usage IN ( %d, %d ) $sql_extra GROUP BY resource_id) ph + ON (p.resource_id = ph.resource_id AND p.imgscale = ph.imgscale) + ORDER BY created $order", + intval($owner_uid), + dbesc($album), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE) + ); + + $photos = array(); + if(count($r)) { + $twist = 'rotright'; + foreach($r as $rr) { + + if($twist == 'rotright') + $twist = 'rotleft'; + else + $twist = 'rotright'; + + $ext = $phototypes[$rr['mimetype']]; + + $imgalt_e = $rr['filename']; + $desc_e = $rr['description']; + + $imagelink = (z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/image/' . $rr['resource_id'] + . (($_GET['order'] === 'posted') ? '?f=&order=posted' : '')); + + $photos[] = array( + 'id' => $rr['id'], + 'twist' => ' ' . $twist . rand(2,4), + 'link' => $imagelink, + 'title' => t('View Photo'), + 'src' => z_root() . '/photo/' . $rr['resource_id'] . '-' . $rr['imgscale'] . '.' .$ext, + 'alt' => $imgalt_e, + 'desc'=> $desc_e, + 'ext' => $ext, + 'hash'=> $rr['resource_id'], + 'unknown' => t('Unknown') + ); + } + } + + $tpl = get_markup_template('photo_album.tpl'); + $o .= replace_macros($tpl, array( + '$photos' => $photos, + '$album' => (($title) ? $title : $album), + '$album_id' => rand(), + '$album_edit' => array(t('Edit Album'), $album_edit), + '$can_post' => false, + '$upload' => array(t('Upload'), z_root() . '/photos/' . \App::$profile['channel_address'] . '/upload/' . bin2hex($album)), + '$order' => false, + '$upload_form' => $upload_form, + '$no_fullscreen_btn' => true + )); + + return $o; +} + + +function embedphotos_album_list($a) { + $o = ''; + require_once('include/photos.php'); + $p = photos_albums_list(\App::get_channel(), \App::get_observer()); + if ($p['success']) { + return $p['albums']; + } else { + return null; + } +} + +} diff --git a/Zotlabs/Module/Events.php b/Zotlabs/Module/Events.php new file mode 100644 index 000000000..def5c437b --- /dev/null +++ b/Zotlabs/Module/Events.php @@ -0,0 +1,718 @@ +set($x[0]); + + $created = $x[0]['created']; + $edited = datetime_convert(); + + if($x[0]['allow_cid'] === '<' . $channel['channel_hash'] . '>' + && $x[0]['allow_gid'] === '' && $x[0]['deny_cid'] === '' && $x[0]['deny_gid'] === '') { + $share = false; + } + else { + $share = true; + } + } + else { + $created = $edited = datetime_convert(); + if($share) { + $acl->set_from_array($_POST); + } + else { + $acl->set(array('allow_cid' => '<' . $channel['channel_hash'] . '>', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '')); + } + } + + $post_tags = array(); + $channel = \App::get_channel(); + $ac = $acl->get(); + + if(strlen($categories)) { + $cats = explode(',',$categories); + foreach($cats as $cat) { + $post_tags[] = array( + 'uid' => $profile_uid, + 'ttype' => TERM_CATEGORY, + 'otype' => TERM_OBJ_POST, + 'term' => trim($cat), + 'url' => $channel['xchan_url'] . '?f=&cat=' . urlencode(trim($cat)) + ); + } + } + + $datarray = array(); + $datarray['dtstart'] = $start; + $datarray['dtend'] = $finish; + $datarray['summary'] = $summary; + $datarray['description'] = $desc; + $datarray['location'] = $location; + $datarray['etype'] = $type; + $datarray['adjust'] = $adjust; + $datarray['nofinish'] = $nofinish; + $datarray['uid'] = local_channel(); + $datarray['account'] = get_account_id(); + $datarray['event_xchan'] = $channel['channel_hash']; + $datarray['allow_cid'] = $ac['allow_cid']; + $datarray['allow_gid'] = $ac['allow_gid']; + $datarray['deny_cid'] = $ac['deny_cid']; + $datarray['deny_gid'] = $ac['deny_gid']; + $datarray['private'] = (($acl->is_private()) ? 1 : 0); + $datarray['id'] = $event_id; + $datarray['created'] = $created; + $datarray['edited'] = $edited; + + if(intval($_REQUEST['preview'])) { + $html = format_event_html($datarray); + echo $html; + killme(); + } + + $event = event_store_event($datarray); + + + if($post_tags) + $datarray['term'] = $post_tags; + + $item_id = event_store_item($datarray,$event); + + if($item_id) { + $r = q("select * from item where id = %d", + intval($item_id) + ); + if($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + $z = q("select * from event where event_hash = '%s' and uid = %d limit 1", + dbesc($r[0]['resource_id']), + intval($channel['channel_id']) + ); + if($z) { + build_sync_packet($channel['channel_id'],array('event_item' => array(encode_item($sync_item[0],true)),'event' => $z)); + } + } + } + + if($share) + \Zotlabs\Daemon\Master::Summon(array('Notifier','event',$item_id)); + + } + + + + function get() { + + if(argc() > 2 && argv(1) == 'ical') { + $event_id = argv(2); + + require_once('include/security.php'); + $sql_extra = permissions_sql(local_channel()); + + $r = q("select * from event where event_hash = '%s' $sql_extra limit 1", + dbesc($event_id) + ); + if($r) { + header('Content-type: text/calendar'); + header('content-disposition: attachment; filename="' . t('event') . '-' . $event_id . '.ics"' ); + echo ical_wrapper($r); + killme(); + } + else { + notice( t('Event not found.') . EOL ); + return; + } + } + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + nav_set_selected('all_events'); + + if((argc() > 2) && (argv(1) === 'ignore') && intval(argv(2))) { + $r = q("update event set dismissed = 1 where id = %d and uid = %d", + intval(argv(2)), + intval(local_channel()) + ); + } + + if((argc() > 2) && (argv(1) === 'unignore') && intval(argv(2))) { + $r = q("update event set dismissed = 0 where id = %d and uid = %d", + intval(argv(2)), + intval(local_channel()) + ); + } + + $first_day = get_pconfig(local_channel(),'system','cal_first_day'); + $first_day = (($first_day) ? $first_day : 0); + + $htpl = get_markup_template('event_head.tpl'); + \App::$page['htmlhead'] .= replace_macros($htpl,array( + '$baseurl' => z_root(), + '$module_url' => '/events', + '$modparams' => 1, + '$lang' => \App::$language, + '$first_day' => $first_day + )); + + $o = ''; + + $channel = \App::get_channel(); + + $mode = 'view'; + $y = 0; + $m = 0; + $ignored = ((x($_REQUEST,'ignored')) ? " and dismissed = " . intval($_REQUEST['ignored']) . " " : ''); + + + // logger('args: ' . print_r(\App::$argv,true)); + + + + if(argc() > 1) { + if(argc() > 2 && argv(1) === 'add') { + $mode = 'add'; + $item_id = intval(argv(2)); + } + if(argc() > 2 && argv(1) === 'drop') { + $mode = 'drop'; + $event_id = argv(2); + } + if(argc() > 2 && intval(argv(1)) && intval(argv(2))) { + $mode = 'view'; + $y = intval(argv(1)); + $m = intval(argv(2)); + } + if(argc() <= 2) { + $mode = 'view'; + $event_id = argv(1); + } + } + + if($mode === 'add') { + event_addtocal($item_id,local_channel()); + killme(); + } + + if($mode == 'view') { + + /* edit/create form */ + if($event_id) { + $r = q("SELECT * FROM `event` WHERE event_hash = '%s' AND `uid` = %d LIMIT 1", + dbesc($event_id), + intval(local_channel()) + ); + if(count($r)) + $orig_event = $r[0]; + } + + $channel = \App::get_channel(); + + // Passed parameters overrides anything found in the DB + if(!x($orig_event)) + $orig_event = array(); + + // In case of an error the browser is redirected back here, with these parameters filled in with the previous values + /* + if(x($_REQUEST,'nofinish')) $orig_event['nofinish'] = $_REQUEST['nofinish']; + if(x($_REQUEST,'adjust')) $orig_event['adjust'] = $_REQUEST['adjust']; + if(x($_REQUEST,'summary')) $orig_event['summary'] = $_REQUEST['summary']; + if(x($_REQUEST,'description')) $orig_event['description'] = $_REQUEST['description']; + if(x($_REQUEST,'location')) $orig_event['location'] = $_REQUEST['location']; + if(x($_REQUEST,'start')) $orig_event['dtstart'] = $_REQUEST['start']; + if(x($_REQUEST,'finish')) $orig_event['dtend'] = $_REQUEST['finish']; + if(x($_REQUEST,'type')) $orig_event['etype'] = $_REQUEST['type']; + */ + + $n_checked = ((x($orig_event) && $orig_event['nofinish']) ? ' checked="checked" ' : ''); + $a_checked = ((x($orig_event) && $orig_event['adjust']) ? ' checked="checked" ' : ''); + $t_orig = ((x($orig_event)) ? $orig_event['summary'] : ''); + $d_orig = ((x($orig_event)) ? $orig_event['description'] : ''); + $l_orig = ((x($orig_event)) ? $orig_event['location'] : ''); + $eid = ((x($orig_event)) ? $orig_event['id'] : 0); + $event_xchan = ((x($orig_event)) ? $orig_event['event_xchan'] : $channel['channel_hash']); + $mid = ((x($orig_event)) ? $orig_event['mid'] : ''); + + if(! x($orig_event)) + $sh_checked = ''; + else + $sh_checked = ((($orig_event['allow_cid'] === '<' . $channel['channel_hash'] . '>' || (! $orig_event['allow_cid'])) && (! $orig_event['allow_gid']) && (! $orig_event['deny_cid']) && (! $orig_event['deny_gid'])) ? '' : ' checked="checked" ' ); + + if($orig_event['event_xchan']) + $sh_checked .= ' disabled="disabled" '; + + $sdt = ((x($orig_event)) ? $orig_event['dtstart'] : 'now'); + + $fdt = ((x($orig_event)) ? $orig_event['dtend'] : '+1 hour'); + + $tz = date_default_timezone_get(); + if(x($orig_event)) + $tz = (($orig_event['adjust']) ? date_default_timezone_get() : 'UTC'); + + $syear = datetime_convert('UTC', $tz, $sdt, 'Y'); + $smonth = datetime_convert('UTC', $tz, $sdt, 'm'); + $sday = datetime_convert('UTC', $tz, $sdt, 'd'); + $shour = datetime_convert('UTC', $tz, $sdt, 'H'); + $sminute = datetime_convert('UTC', $tz, $sdt, 'i'); + + $stext = datetime_convert('UTC',$tz,$sdt); + $stext = substr($stext,0,14) . "00:00"; + + $fyear = datetime_convert('UTC', $tz, $fdt, 'Y'); + $fmonth = datetime_convert('UTC', $tz, $fdt, 'm'); + $fday = datetime_convert('UTC', $tz, $fdt, 'd'); + $fhour = datetime_convert('UTC', $tz, $fdt, 'H'); + $fminute = datetime_convert('UTC', $tz, $fdt, 'i'); + + $ftext = datetime_convert('UTC',$tz,$fdt); + $ftext = substr($ftext,0,14) . "00:00"; + + $type = ((x($orig_event)) ? $orig_event['etype'] : 'event'); + + $f = get_config('system','event_input_format'); + if(! $f) + $f = 'ymd'; + + $catsenabled = feature_enabled(local_channel(),'categories'); + + $category = ''; + + if($catsenabled && x($orig_event)){ + $itm = q("select * from item where resource_type = 'event' and resource_id = '%s' and uid = %d limit 1", + dbesc($orig_event['event_hash']), + intval(local_channel()) + ); + $itm = fetch_post_tags($itm); + if($itm) { + $cats = get_terms_oftype($itm[0]['term'], TERM_CATEGORY); + foreach ($cats as $cat) { + if(strlen($category)) + $category .= ', '; + $category .= $cat['term']; + } + } + } + + require_once('include/acl_selectors.php'); + + $acl = new \Zotlabs\Access\AccessList($channel); + $perm_defaults = $acl->get(); + + $tpl = get_markup_template('event_form.tpl'); + + $form = replace_macros($tpl,array( + '$post' => z_root() . '/events', + '$eid' => $eid, + '$type' => $type, + '$xchan' => $event_xchan, + '$mid' => $mid, + '$event_hash' => $event_id, + '$summary' => array('summary', (($event_id) ? t('Edit event title') : t('Event title')), $t_orig, t('Required'), '*'), + '$catsenabled' => $catsenabled, + '$placeholdercategory' => t('Categories (comma-separated list)'), + '$c_text' => (($event_id) ? t('Edit Category') : t('Category')), + '$category' => $category, + '$required' => '*', + '$s_dsel' => datetimesel($f,new \DateTime(),\DateTime::createFromFormat('Y',$syear+5),\DateTime::createFromFormat('Y-m-d H:i',"$syear-$smonth-$sday $shour:$sminute"), (($event_id) ? t('Edit start date and time') : t('Start date and time')), 'start_text',true,true,'','',true,$first_day), + '$n_text' => t('Finish date and time are not known or not relevant'), + '$n_checked' => $n_checked, + '$f_dsel' => datetimesel($f,new \DateTime(),\DateTime::createFromFormat('Y',$fyear+5),\DateTime::createFromFormat('Y-m-d H:i',"$fyear-$fmonth-$fday $fhour:$fminute"), (($event_id) ? t('Edit finish date and time') : t('Finish date and time')),'finish_text',true,true,'start_text','',false,$first_day), + '$nofinish' => array('nofinish', t('Finish date and time are not known or not relevant'), $n_checked, '', array(t('No'),t('Yes')), 'onclick="enableDisableFinishDate();"'), + '$adjust' => array('adjust', t('Adjust for viewer timezone'), $a_checked, t('Important for events that happen in a particular place. Not practical for global holidays.'), array(t('No'),t('Yes'))), + '$a_text' => t('Adjust for viewer timezone'), + '$d_text' => (($event_id) ? t('Edit Description') : t('Description')), + '$d_orig' => $d_orig, + '$l_text' => (($event_id) ? t('Edit Location') : t('Location')), + '$l_orig' => $l_orig, + '$t_orig' => $t_orig, + '$sh_text' => t('Share this event'), + '$sh_checked' => $sh_checked, + '$share' => array('share', t('Share this event'), $sh_checked, '', array(t('No'),t('Yes'))), + '$preview' => t('Preview'), + '$permissions' => t('Permission settings'), + // populating the acl dialog was a permission description from view_stream because Cal.php, which + // displays events, says "since we don't currently have an event permission - use the stream permission" + '$acl' => (($orig_event['event_xchan']) ? '' : populate_acl(((x($orig_event)) ? $orig_event : $perm_defaults), false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'))), + '$submit' => t('Submit'), + '$advanced' => t('Advanced Options') + + )); + /* end edit/create form */ + + $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); + $thismonth = datetime_convert('UTC',date_default_timezone_get(),'now','m'); + if(! $y) + $y = intval($thisyear); + if(! $m) + $m = intval($thismonth); + + $export = false; + if(argc() === 4 && argv(3) === 'export') + $export = true; + + // Put some limits on dates. The PHP date functions don't seem to do so well before 1900. + // An upper limit was chosen to keep search engines from exploring links millions of years in the future. + + if($y < 1901) + $y = 1900; + if($y > 2099) + $y = 2100; + + $nextyear = $y; + $nextmonth = $m + 1; + if($nextmonth > 12) { + $nextmonth = 1; + $nextyear ++; + } + + $prevyear = $y; + if($m > 1) + $prevmonth = $m - 1; + else { + $prevmonth = 12; + $prevyear --; + } + + $dim = get_dim($y,$m); + $start = sprintf('%d-%d-%d %d:%d:%d',$y,$m,1,0,0,0); + $finish = sprintf('%d-%d-%d %d:%d:%d',$y,$m,$dim,23,59,59); + + + if (argv(1) === 'json'){ + if (x($_GET,'start')) $start = $_GET['start']; + if (x($_GET,'end')) $finish = $_GET['end']; + } + + $start = datetime_convert('UTC','UTC',$start); + $finish = datetime_convert('UTC','UTC',$finish); + + $adjust_start = datetime_convert('UTC', date_default_timezone_get(), $start); + $adjust_finish = datetime_convert('UTC', date_default_timezone_get(), $finish); + + if (x($_GET,'id')){ + $r = q("SELECT event.*, item.plink, item.item_flags, item.author_xchan, item.owner_xchan + from event left join item on resource_id = event_hash where resource_type = 'event' and event.uid = %d and event.id = %d limit 1", + intval(local_channel()), + intval($_GET['id']) + ); + } elseif($export) { + $r = q("SELECT * from event where uid = %d + AND (( `adjust` = 0 AND ( `dtend` >= '%s' or nofinish = 1 ) AND `dtstart` <= '%s' ) + OR ( `adjust` = 1 AND ( `dtend` >= '%s' or nofinish = 1 ) AND `dtstart` <= '%s' )) ", + intval(local_channel()), + dbesc($start), + dbesc($finish), + dbesc($adjust_start), + dbesc($adjust_finish) + ); + } + else { + // fixed an issue with "nofinish" events not showing up in the calendar. + // There's still an issue if the finish date crosses the end of month. + // Noting this for now - it will need to be fixed here and in Friendica. + // Ultimately the finish date shouldn't be involved in the query. + + $r = q("SELECT event.*, item.plink, item.item_flags, item.author_xchan, item.owner_xchan + from event left join item on event_hash = resource_id + where resource_type = 'event' and event.uid = %d $ignored + AND (( adjust = 0 AND ( dtend >= '%s' or nofinish = 1 ) AND dtstart <= '%s' ) + OR ( adjust = 1 AND ( dtend >= '%s' or nofinish = 1 ) AND dtstart <= '%s' )) ", + intval(local_channel()), + dbesc($start), + dbesc($finish), + dbesc($adjust_start), + dbesc($adjust_finish) + ); + + } + + $links = array(); + + if($r && ! $export) { + xchan_query($r); + $r = fetch_post_tags($r,true); + + $r = sort_by_date($r); + } + + if($r) { + foreach($r as $rr) { + $j = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'j') : datetime_convert('UTC','UTC',$rr['dtstart'],'j')); + if(! x($links,$j)) + $links[$j] = z_root() . '/' . \App::$cmd . '#link-' . $j; + } + } + + $events=array(); + + $last_date = ''; + $fmt = t('l, F j'); + + if($r) { + + foreach($r as $rr) { + + $j = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'j') : datetime_convert('UTC','UTC',$rr['dtstart'],'j')); + $d = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], $fmt) : datetime_convert('UTC','UTC',$rr['dtstart'],$fmt)); + $d = day_translate($d); + + $start = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtstart'], 'c') : datetime_convert('UTC','UTC',$rr['dtstart'],'c')); + if ($rr['nofinish']){ + $end = null; + } else { + $end = (($rr['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$rr['dtend'], 'c') : datetime_convert('UTC','UTC',$rr['dtend'],'c')); + } + + + $is_first = ($d !== $last_date); + + $last_date = $d; + + $edit = ((local_channel() && $rr['author_xchan'] == get_observer_hash()) ? array(z_root().'/events/'.$rr['event_hash'].'?expandform=1',t('Edit event'),'','') : false); + + $drop = array(z_root().'/events/drop/'.$rr['event_hash'],t('Delete event'),'',''); + + $title = strip_tags(html_entity_decode(bbcode($rr['summary']),ENT_QUOTES,'UTF-8')); + if(! $title) { + list($title, $_trash) = explode("$rr['id'], + 'hash' => $rr['event_hash'], + 'start'=> $start, + 'end' => $end, + 'drop' => $drop, + 'allDay' => false, + 'title' => $title, + + 'j' => $j, + 'd' => $d, + 'edit' => $edit, + 'is_first'=>$is_first, + 'item'=>$rr, + 'html'=>$html, + 'plink' => array($rr['plink'],t('Link to Source'),'',''), + ); + + + } + } + + if($export) { + header('Content-type: text/calendar'); + header('content-disposition: attachment; filename="' . t('calendar') . '-' . $channel['channel_address'] . '.ics"' ); + echo ical_wrapper($r); + killme(); + } + + if (\App::$argv[1] === 'json'){ + echo json_encode($events); killme(); + } + + // links: array('href', 'text', 'extra css classes', 'title') + if (x($_GET,'id')){ + $tpl = get_markup_template("event.tpl"); + } + else { + $tpl = get_markup_template("events-js.tpl"); + } + + $o = replace_macros($tpl, array( + '$baseurl' => z_root(), + '$new_event' => array(z_root().'/events',(($event_id) ? t('Edit Event') : t('Create Event')),'',''), + '$previus' => array(z_root()."/events/$prevyear/$prevmonth",t('Previous'),'',''), + '$next' => array(z_root()."/events/$nextyear/$nextmonth",t('Next'),'',''), + '$export' => array(z_root()."/events/$y/$m/export",t('Export'),'',''), + '$calendar' => cal($y,$m,$links, ' eventcal'), + '$events' => $events, + '$view_label' => t('View'), + '$month' => t('Month'), + '$week' => t('Week'), + '$day' => t('Day'), + '$prev' => t('Previous'), + '$next' => t('Next'), + '$today' => t('Today'), + '$form' => $form, + '$expandform' => ((x($_GET,'expandform')) ? true : false), + )); + + if (x($_GET,'id')){ echo $o; killme(); } + + return $o; + } + + if($mode === 'drop' && $event_id) { + $r = q("SELECT * FROM `event` WHERE event_hash = '%s' AND `uid` = %d LIMIT 1", + dbesc($event_id), + intval(local_channel()) + ); + + $sync_event = $r[0]; + + if($r) { + $r = q("delete from event where event_hash = '%s' and uid = %d limit 1", + dbesc($event_id), + intval(local_channel()) + ); + if($r) { + $r = q("update item set resource_type = '', resource_id = '' where resource_type = 'event' and resource_id = '%s' and uid = %d", + dbesc($event_id), + intval(local_channel()) + ); + $sync_event['event_deleted'] = 1; + build_sync_packet(0,array('event' => array($sync_event))); + + info( t('Event removed') . EOL); + } + else { + notice( t('Failed to remove event' ) . EOL); + } + goaway(z_root() . '/events'); + } + } + + } + +} diff --git a/Zotlabs/Module/Fbrowser.php b/Zotlabs/Module/Fbrowser.php new file mode 100644 index 000000000..c534e8f72 --- /dev/null +++ b/Zotlabs/Module/Fbrowser.php @@ -0,0 +1,136 @@ + + */ + +require_once('include/photo/photo_driver.php'); + +/** + * @param App $a + */ + +class Fbrowser extends \Zotlabs\Web\Controller { + + function get(){ + + if (!local_channel()) + killme(); + + if (\App::$argc==1) + killme(); + + //echo "
"; var_dump(\App::$argv); killme();	
+		
+		switch(\App::$argv[1]){
+			case "image":
+				$path = array( array(z_root()."/fbrowser/image/", t("Photos")));
+				$albums = false;
+				$sql_extra = "";
+				$sql_extra2 = " ORDER BY created DESC LIMIT 0, 10";
+				
+				if (\App::$argc==2){
+					$albums = q("SELECT distinct(`album`) AS `album` FROM `photo` WHERE `uid` = %d ",
+						intval(local_channel())
+					);
+					// anon functions only from 5.3.0... meglio tardi che mai..
+					$albums = array_map( "self::folder1" , $albums);
+					
+				}
+				
+				$album = "";
+				if (\App::$argc==3){
+					$album = hex2bin(\App::$argv[2]);
+					$sql_extra = sprintf("AND `album` = '%s' ",dbesc($album));
+					$sql_extra2 = "";
+					$path[]=array(z_root() . "/fbrowser/image/" . \App::$argv[2] . "/", $album);
+				}
+					
+				$r = q("SELECT `resource_id`, `id`, `filename`, type, min(`imgscale`) AS `hiq`,max(`imgscale`) AS `loq`, `description`  
+						FROM `photo` WHERE `uid` = %d $sql_extra
+						GROUP BY `resource_id` $sql_extra2",
+					intval(local_channel())					
+				);
+				
+				$files = array_map("self::files1", $r);
+				
+				$tpl = get_markup_template("filebrowser.tpl");
+				echo replace_macros($tpl, array(
+					'$type' => 'image',
+					'$baseurl' => z_root(),
+					'$path' => $path,
+					'$folders' => $albums,
+					'$files' =>$files,
+					'$cancel' => t('Cancel'),
+				));
+					
+					
+				break;
+			case "file":
+				if (\App::$argc==2){
+					$files = q("SELECT id, filename, filetype FROM `attach` WHERE `uid` = %d ",
+						intval(local_channel())
+					);
+					
+					$files = array_map("self::files2", $files);
+					//echo "
"; var_dump($files); killme();
+				
+								
+					$tpl = get_markup_template("filebrowser.tpl");
+					echo replace_macros($tpl, array(
+						'$type' => 'file',
+						'$baseurl' => z_root(),
+						'$path' => array( array(z_root()."/fbrowser/image/", t("Files")) ),
+						'$folders' => false,
+						'$files' =>$files,
+						'$cancel' => t('Cancel'),
+					));
+					
+				}
+			
+				break;
+		}
+		
+	
+		killme();
+		
+	}
+
+	private static function folder1($el){
+		return array(bin2hex($el['album']),$el['album']);
+	}	
+
+
+	private static function files1($rr){ 
+
+		$ph = photo_factory('');
+		$types = $ph->supportedTypes();
+		$ext = $types[$rr['type']];
+	
+		$filename_e = $rr['filename'];
+			
+		return array( 
+			z_root() . '/photo/' . $rr['resource_id'] . '-' . $rr['hiq'] . '.' .$ext, 
+			$filename_e, 
+			z_root() . '/photo/' . $rr['resource_id'] . '-' . $rr['loq'] . '.'. $ext
+		);
+	}
+
+	private static function files2($rr){
+		list($m1,$m2) = explode("/",$rr['filetype']);
+		$filetype = ( (file_exists("images/icons/$m1.png"))?$m1:"zip");
+	
+		if(\App::get_template_engine() === 'internal') {
+			$filename_e = template_escape($rr['filename']);
+		}
+		else {
+			$filename_e = $rr['filename'];
+		}
+	
+		return array( z_root() . '/attach/' . $rr['id'], $filename_e, z_root() . '/images/icons/16/' . $filetype . '.png'); 
+	}
+
+	
+}
diff --git a/Zotlabs/Module/Feed.php b/Zotlabs/Module/Feed.php
new file mode 100644
index 000000000..47871eafb
--- /dev/null
+++ b/Zotlabs/Module/Feed.php
@@ -0,0 +1,48 @@
+ 1) {
+			$r = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_address = '%s' limit 1",
+				dbesc(argv(1))
+			);
+			if(!($r && count($r)))
+				killme();
+	
+			$channel = $r[0];
+	
+			if(observer_prohibited(true))
+				killme();
+	 
+			logger('mod_feed: public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $channel['channel_address']);
+	
+			echo get_public_feed($channel,$params);
+	
+			killme();
+		}
+	
+	}
+	
+	
+	
+}
diff --git a/Zotlabs/Module/Ffsapi.php b/Zotlabs/Module/Ffsapi.php
new file mode 100644
index 000000000..f3ade73c2
--- /dev/null
+++ b/Zotlabs/Module/Ffsapi.php
@@ -0,0 +1,71 @@
+
+	
+	var baseurl = '$baseurl';
+	
+	var data = {
+	  "origin": baseurl,
+	  // currently required
+	  "name": '$name',
+	  "iconURL": baseurl+"/images/hz-16.png",
+	  "icon32URL": baseurl+"/images/hz-32.png",
+	  "icon64URL": baseurl+"/images/hz-64.png",
+	
+	  // at least one of these must be defined
+	  // "workerURL": baseurl+"/worker.js",
+	  // "sidebarURL": baseurl+"/sidebar.htm",
+	  "shareURL": baseurl+"/rpost?f=&url=%{url}",
+	
+	  // status buttons are scheduled for Firefox 26 or 27
+	  //"statusURL": baseurl+"/statusPanel.html",
+	
+	  // social bookmarks are available in Firefox 26
+	  "markURL": baseurl+"/rbmark?f=&url=%{url}&title=%{title}",
+	  // icons should be 32x32 pixels
+	  // "markedIcon": baseurl+"/images/checkbox-checked-32.png",
+	  // "unmarkedIcon": baseurl+"/images/checkbox-unchecked-32.png",
+	  "unmarkedIcon": baseurl+"/images/hz-bookmark-32.png",
+	
+	  // should be available for display purposes
+	  "description": "$description",
+	  "author": "$author",
+	  "homepageURL": "$homepage",
+	
+	  // optional
+	  "version": "1.0"
+	}
+	
+	function activate(node) {
+	  var event = new CustomEvent("ActivateSocialFeature");
+	  var jdata = JSON.stringify(data);
+	  node.setAttribute("data-service", JSON.stringify(data));
+	  node.dispatchEvent(event);
+	}
+	
+	
+	
+	
+EOT;
+	
+	return $s;
+	
+	}
+	
+}
diff --git a/Zotlabs/Module/Fhublocs.php b/Zotlabs/Module/Fhublocs.php
new file mode 100644
index 000000000..f5b439421
--- /dev/null
+++ b/Zotlabs/Module/Fhublocs.php
@@ -0,0 +1,85 @@
+ 1) ? intval(\App::$argv[1]) : 0);
+	
+		logger('filer: tag ' . $term . ' item ' . $item_id);
+	
+		if($item_id && strlen($term)){
+			// file item
+			store_item_tag(local_channel(),$item_id,TERM_OBJ_POST,TERM_FILE,$term,'');
+	
+			// protect the entire conversation from periodic expiration
+	
+			$r = q("select parent from item where id = %d and uid = %d limit 1",
+				intval($item_id),
+				intval(local_channel())
+			);
+			if($r) {
+				$x = q("update item set item_retained = 1 where id = %d and uid = %d",
+					intval($r[0]['parent']),
+					intval(local_channel())
+				);
+			}
+		} 
+		else {
+			$filetags = array();
+			$r = q("select distinct(term) from term where uid = %d and ttype = %d order by term asc",
+				intval(local_channel()),
+				intval(TERM_FILE)
+			);
+			if(count($r)) {
+				foreach($r as $rr)
+					$filetags[] = $rr['term'];
+			}
+			$tpl = get_markup_template("filer_dialog.tpl");
+			$o = replace_macros($tpl, array(
+				'$field' => array('term', t("Save to Folder:"), '', '', $filetags, t('- select -')),
+				'$submit' => t('Save'),
+			));
+			
+			echo $o;
+		}
+		killme();
+	}
+	
+}
diff --git a/Zotlabs/Module/Filerm.php b/Zotlabs/Module/Filerm.php
new file mode 100644
index 000000000..cbf6a118d
--- /dev/null
+++ b/Zotlabs/Module/Filerm.php
@@ -0,0 +1,39 @@
+ 1) ? intval(\App::$argv[1]) : 0);
+	
+		logger('filerm: tag ' . $term . ' item ' . $item_id);
+	
+		if($item_id && strlen($term)) {
+			$r = q("delete from term where uid = %d and ttype = %d and oid = %d and term = '%s'",
+				intval(local_channel()),
+				intval(($category) ? TERM_CATEGORY : TERM_FILE),
+				intval($item_id),
+				dbesc($term)
+			);
+		}
+	
+		if(x($_SESSION,'return_url'))
+			goaway(z_root() . '/' . $_SESSION['return_url']);
+		
+		killme();
+	}
+	
+}
diff --git a/Zotlabs/Module/Filestorage.php b/Zotlabs/Module/Filestorage.php
new file mode 100644
index 000000000..c3ef22e32
--- /dev/null
+++ b/Zotlabs/Module/Filestorage.php
@@ -0,0 +1,174 @@
+set_from_array($_REQUEST);
+		$x = $acl->get();
+	
+		$cloudPath = get_parent_cloudpath($channel_id, $channel['channel_address'], $resource);
+	
+		//get the object before permissions change so we can catch eventual former allowed members
+		$object = get_file_activity_object($channel_id, $resource, $cloudPath);
+	
+		attach_change_permissions($channel_id, $resource, $x['allow_cid'], $x['allow_gid'], $x['deny_cid'], $x['deny_gid'], $recurse);
+	
+		file_activity($channel_id, $object, $x['allow_cid'], $x['allow_gid'], $x['deny_cid'], $x['deny_gid'], 'post', $notify);
+	
+		goaway($cloudPath);
+	}
+	
+		function get() {
+	
+		if(argc() > 1)
+			$which = argv(1);
+		else {
+			notice( t('Requested profile is not available.') . EOL );
+			\App::$error = 404;
+			return;
+		}
+	
+		$r = q("select * from channel where channel_address = '%s'",
+			dbesc($which)
+		);
+		if($r) {
+			$channel = $r[0];
+			$owner = intval($r[0]['channel_id']);
+		}
+	
+		$observer = \App::get_observer();
+		$ob_hash = (($observer) ? $observer['xchan_hash'] : '');
+	
+		$perms = get_all_perms($owner, $ob_hash);
+	
+		if(! $perms['view_storage']) {
+			notice( t('Permission denied.') . EOL);
+			return;
+		}
+	
+		// Since we have ACL'd files in the wild, but don't have ACL here yet, we
+		// need to return for anyone other than the owner, despite the perms check for now.
+	
+		$is_owner = (((local_channel()) && ($owner  == local_channel())) ? true : false);
+		if(! $is_owner) {
+			info( t('Permission Denied.') . EOL );
+			return;
+		}
+	
+		if(argc() > 3 && argv(3) === 'delete') {
+			if(! $perms['write_storage']) {
+				notice( t('Permission denied.') . EOL);
+				return;
+			}
+	
+			$file = intval(argv(2));
+			$r = q("SELECT hash FROM attach WHERE id = %d AND uid = %d LIMIT 1",
+				dbesc($file),
+				intval($owner)
+			);
+			if(! $r) {
+				notice( t('File not found.') . EOL);
+				goaway(z_root() . '/cloud/' . $which);
+			}
+	
+			$f = $r[0];
+			$channel = \App::get_channel();
+	
+			$parentpath = get_parent_cloudpath($channel['channel_id'], $channel['channel_address'], $f['hash']);
+	
+			attach_delete($owner, $f['hash']);
+	
+			goaway($parentpath);
+		}
+	
+		if(argc() > 3 && argv(3) === 'edit') {
+			require_once('include/acl_selectors.php');
+			if(! $perms['write_storage']) {
+				notice( t('Permission denied.') . EOL);
+				return;
+			}
+			$file = intval(argv(2));
+	
+			$r = q("select id, uid, folder, filename, revision, flags, is_dir, os_storage, hash, allow_cid, allow_gid, deny_cid, deny_gid from attach where id = %d and uid = %d limit 1",
+				intval($file),
+				intval($owner)
+			);
+	
+			$f = $r[0];
+			$channel = \App::get_channel();
+	
+			$cloudpath = get_cloudpath($f) . (intval($f['is_dir']) ? '?f=&davguest=1' : '');
+			$parentpath = get_parent_cloudpath($channel['channel_id'], $channel['channel_address'], $f['hash']);
+	
+			$aclselect_e = populate_acl($f, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_storage'));
+			$is_a_dir = (intval($f['is_dir']) ? true : false);
+	
+			$lockstate = (($f['allow_cid'] || $f['allow_gid'] || $f['deny_cid'] || $f['deny_gid']) ? 'lock' : 'unlock'); 
+	
+			// Encode path that is used for link so it's a valid URL
+			// Keep slashes as slashes, otherwise mod_rewrite doesn't work correctly
+			$encoded_path = str_replace('%2F', '/', rawurlencode($cloudpath));
+	
+			$o = replace_macros(get_markup_template('attach_edit.tpl'), array(
+				'$header' => t('Edit file permissions'),
+				'$file' => $f,
+				'$cloudpath' => z_root() . '/' . $encoded_path,
+				'$parentpath' => $parentpath,
+				'$uid' => $channel['channel_id'],
+				'$channelnick' => $channel['channel_address'],
+				'$permissions' => t('Permissions'),
+				'$aclselect' => $aclselect_e,
+				'$lockstate' => $lockstate,
+				'$permset' => t('Set/edit permissions'),
+				'$recurse' => array('recurse', t('Include all files and sub folders'), 0, '', array(t('No'), t('Yes'))),
+				'$backlink' => t('Return to file list'),
+				'$isadir' => $is_a_dir,
+				'$cpdesc' => t('Copy/paste this code to attach file to a post'),
+				'$cpldesc' => t('Copy/paste this URL to link file from a web page'),
+				'$submit' => t('Submit'),
+				'$attach_btn_title' => t('Share this file'),
+				'$link_btn_title' => t('Show URL to this file'),
+				'$notify' => array('notify', t('Notify your contacts about this file'), 0, '', array(t('No'), t('Yes')))
+			));
+	
+			echo $o;
+			killme();
+		}
+	
+		goaway(z_root() . '/cloud/' . $which);
+	}
+	
+}
diff --git a/Zotlabs/Module/Follow.php b/Zotlabs/Module/Follow.php
new file mode 100644
index 000000000..da9ab3670
--- /dev/null
+++ b/Zotlabs/Module/Follow.php
@@ -0,0 +1,68 @@
+ $v) {
+			if(strpos($k,'abook_') === 0) {
+				$clone[$k] = $v;
+			}
+		}
+		unset($clone['abook_id']);
+		unset($clone['abook_account']);
+		unset($clone['abook_channel']);
+	
+		$abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']);
+		if($abconfig)
+			$clone['abconfig'] = $abconfig;
+	
+		build_sync_packet(0 /* use the current local_channel */, array('abook' => array($clone)), true);
+	
+		$can_view_stream = intval(get_abconfig($channel['channel_id'],$clone['abook_xchan'],'their_perms','view_stream'));
+	
+		// If we can view their stream, pull in some posts
+	
+		if(($can_view_stream) || ($result['abook']['xchan_network'] === 'rss'))
+			\Zotlabs\Daemon\Master::Summon(array('Onepoll',$result['abook']['abook_id']));
+	
+		goaway(z_root() . '/connedit/' . $result['abook']['abook_id'] . '?f=&follow=1');
+	
+	}
+	
+	function get() {
+		if(! local_channel()) {
+			return login();
+		}
+	}
+}
diff --git a/Zotlabs/Module/Getfile.php b/Zotlabs/Module/Getfile.php
new file mode 100644
index 000000000..09d761887
--- /dev/null
+++ b/Zotlabs/Module/Getfile.php
@@ -0,0 +1,101 @@
+ $d1) || ($time < $d2)) {
+			logger('time outside allowable range');
+			killme();
+		}
+	
+		if(! rsa_verify($hash . '.' . $time,base64url_decode($sig),$channel['channel_pubkey'])) {
+			logger('verify failed.');
+			killme();
+		}
+	
+	
+		$r = attach_by_hash($resource,$revision);
+	
+		if(! $r['success']) {
+			notice( $r['message'] . EOL);
+			return;
+		}
+		
+	
+		$unsafe_types = array('text/html','text/css','application/javascript');
+	
+		if(in_array($r['data']['filetype'],$unsafe_types)) {
+				header('Content-type: text/plain');
+		}
+		else {
+			header('Content-type: ' . $r['data']['filetype']);
+		}
+	
+		header('Content-disposition: attachment; filename="' . $r['data']['filename'] . '"');
+		if(intval($r['data']['os_storage'])) {
+			$fname = dbunescbin($r['data']['data']);
+			if(strpos($fname,'store') !== false)
+				$istream = fopen($fname,'rb');
+			else
+				$istream = fopen('store/' . $channel['channel_address'] . '/' . $fname,'rb');
+			$ostream = fopen('php://output','wb');
+			if($istream && $ostream) {
+				pipe_streams($istream,$ostream);
+				fclose($istream);
+				fclose($ostream);
+			}
+		}
+		else
+			echo dbunescbin($r['data']['data']);
+		killme();
+	
+	
+	
+	}
+}
diff --git a/Zotlabs/Module/Group.php b/Zotlabs/Module/Group.php
new file mode 100644
index 000000000..254ee6ef2
--- /dev/null
+++ b/Zotlabs/Module/Group.php
@@ -0,0 +1,244 @@
+ t('Submit'));
+	
+		if((argc() == 2) && (argv(1) === 'new')) {
+			
+			return replace_macros($tpl, $context + array(
+				'$title' => t('Create a group of channels.'),
+				'$gname' => array('groupname',t('Privacy group name: '), '', ''),
+				'$gid' => 'new',
+				'$public' => array('public',t('Members are visible to other channels'), false, ''),
+				'$form_security_token' => get_form_security_token("group_edit"),
+			));
+	
+	
+		}
+	
+		if((argc() == 3) && (argv(1) === 'drop')) {
+			check_form_security_token_redirectOnErr('/group', 'group_drop', 't');
+			
+			if(intval(argv(2))) {
+				$r = q("SELECT `name` FROM `groups` WHERE `id` = %d AND `uid` = %d LIMIT 1",
+					intval(argv(2)),
+					intval(local_channel())
+				);
+				if($r) 
+					$result = group_rmv(local_channel(),$r[0]['gname']);
+				if($result)
+					info( t('Privacy group removed.') . EOL);
+				else
+					notice( t('Unable to remove privacy group.') . EOL);
+			}
+			goaway(z_root() . '/group');
+			// NOTREACHED
+		}
+	
+	
+		if((argc() > 2) && intval(argv(1)) && argv(2)) {
+	
+			check_form_security_token_ForbiddenOnErr('group_member_change', 't');
+	
+			$r = q("SELECT abook_xchan from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d and xchan_deleted = 0 and abook_self = 0 and abook_blocked = 0 and abook_pending = 0 limit 1",
+				dbesc(base64url_decode(argv(2))),
+				intval(local_channel())
+			);
+			if(count($r))
+				$change = base64url_decode(argv(2));
+	
+		}
+	
+		if((argc() > 1) && (intval(argv(1)))) {
+	
+			require_once('include/acl_selectors.php');
+			$r = q("SELECT * FROM `groups` WHERE `id` = %d AND `uid` = %d AND `deleted` = 0 LIMIT 1",
+				intval(argv(1)),
+				intval(local_channel())
+			);
+			if(! $r) {
+				notice( t('Privacy group not found.') . EOL );
+				goaway(z_root() . '/connections');
+			}
+			$group = $r[0];
+	
+	
+			$members = group_get_members($group['id']);
+	
+			$preselected = array();
+			if(count($members))	{
+				foreach($members as $member)
+					if(! in_array($member['xchan_hash'],$preselected))
+						$preselected[] = $member['xchan_hash'];
+			}
+	
+			if($change) {
+	
+				if(in_array($change,$preselected)) {
+					group_rmv_member(local_channel(),$group['gname'],$change);
+				}
+				else {
+					group_add_member(local_channel(),$group['gname'],$change);
+				}
+	
+				$members = group_get_members($group['id']);
+	
+				$preselected = array();
+				if(count($members))	{
+					foreach($members as $member)
+						$preselected[] = $member['xchan_hash'];
+				}
+			}
+	
+			$drop_tpl = get_markup_template('group_drop.tpl');
+			$drop_txt = replace_macros($drop_tpl, array(
+				'$id' => $group['id'],
+				'$delete' => t('Delete'),
+				'$form_security_token' => get_form_security_token("group_drop"),
+			));
+	
+			
+			$context = $context + array(
+				'$title' => t('Privacy group editor'),
+				'$gname' => array('groupname',t('Privacy group name: '),$group['gname'], ''),
+				'$gid' => $group['id'],
+				'$drop' => $drop_txt,
+				'$public' => array('public',t('Members are visible to other channels'), $group['visible'], ''),
+				'$form_security_token' => get_form_security_token('group_edit'),
+			);
+	
+		}
+	
+		if(! isset($group))
+			return;
+	
+		$groupeditor = array(
+			'label_members' => t('Members'),
+			'members' => array(),
+			'label_contacts' => t('All Connected Channels'),
+			'contacts' => array(),
+		);
+			
+		$sec_token = addslashes(get_form_security_token('group_member_change'));
+		$textmode = (($switchtotext && (count($members) > $switchtotext)) ? true : false);
+		foreach($members as $member) {
+			if($member['xchan_url']) {
+				$member['archived'] = (intval($member['abook_archived']) ? true : false);
+				$member['click'] = 'groupChangeMember(' . $group['id'] . ',\'' . base64url_encode($member['xchan_hash']) . '\',\'' . $sec_token . '\'); return false;';
+				$groupeditor['members'][] = micropro($member,true,'mpgroup', $textmode);
+			}
+			else
+				group_rmv_member(local_channel(),$group['gname'],$member['xchan_hash']);
+		}
+	
+		$r = q("SELECT abook.*, xchan.* FROM `abook` left join xchan on abook_xchan = xchan_hash WHERE `abook_channel` = %d AND abook_self = 0 and abook_blocked = 0 and abook_pending = 0 and xchan_deleted = 0 order by xchan_name asc",
+			intval(local_channel())
+		);
+	
+		if(count($r)) {
+			$textmode = (($switchtotext && (count($r) > $switchtotext)) ? true : false);
+			foreach($r as $member) {
+				if(! in_array($member['xchan_hash'],$preselected)) {
+					$member['archived'] = (intval($member['abook_archived']) ? true : false);
+					$member['click'] = 'groupChangeMember(' . $group['id'] . ',\'' . base64url_encode($member['xchan_hash']) . '\',\'' . $sec_token . '\'); return false;';
+					$groupeditor['contacts'][] = micropro($member,true,'mpall', $textmode);
+				}
+			}
+		}
+	
+		$context['$groupeditor'] = $groupeditor;
+		$context['$desc'] = t('Click on a channel to add or remove.');
+	
+		if($change) {
+			$tpl = get_markup_template('groupeditor.tpl');
+			echo replace_macros($tpl, $context);
+			killme();
+		}
+		
+		return replace_macros($tpl, $context);
+	
+	}
+	
+	
+}
diff --git a/Zotlabs/Module/Hcard.php b/Zotlabs/Module/Hcard.php
new file mode 100644
index 000000000..93c8d3ece
--- /dev/null
+++ b/Zotlabs/Module/Hcard.php
@@ -0,0 +1,60 @@
+ 1)
+	        $which = argv(1);
+	    else {
+	        notice( t('Requested profile is not available.') . EOL );
+	        \App::$error = 404;
+	        return;
+	    }
+	
+	    $profile = '';
+	    $channel = \App::get_channel();
+	
+	    if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) {
+	        $which = $channel['channel_address'];
+	        $profile = argv(1);
+	        $r = q("select profile_guid from profile where id = %d and uid = %d limit 1",
+	            intval($profile),
+	            intval(local_channel())
+	        );
+	        if(! $r)
+	            $profile = '';
+	        $profile = $r[0]['profile_guid'];
+	    }
+	
+	    \App::$page['htmlhead'] .= '' . "\r\n" ;
+	
+	    if(! $profile) {
+	        $x = q("select channel_id as profile_uid from channel where channel_address = '%s' limit 1",
+	            dbesc(argv(1))
+	        );
+	        if($x) {
+	            \App::$profile = $x[0];
+	        }
+	    }
+	
+		profile_load($which,$profile);
+	
+	
+	}
+	
+	
+		function get() {
+	
+		require_once('include/widgets.php');
+		return widget_profile(array());
+	
+	
+	
+	}
+	
+	
+	
+}
diff --git a/Zotlabs/Module/Help.php b/Zotlabs/Module/Help.php
new file mode 100644
index 000000000..cc46c550b
--- /dev/null
+++ b/Zotlabs/Module/Help.php
@@ -0,0 +1,146 @@
+';
+			$o .= '
'; + $o .= '

' . t('Documentation Search') . ' - ' . htmlspecialchars($_REQUEST['search']) . '

'; + $o .= '
'; + $o .= '
'; + + $r = search_doc_files($_REQUEST['search']); + if($r) { + $o .= '
    '; + foreach($r as $rr) { + $dirname = dirname($rr['sid']); + $fname = basename($rr['sid']); + $fname = substr($fname,0,strrpos($fname,'.')); + $path = trim(substr($dirname,4),'/'); + + $o .= '
  • ' . ucwords(str_replace('_',' ',notags($fname))) . '
    ' . + str_replace('$Projectname',\Zotlabs\Lib\System::get_platform_name(),substr($rr['text'],0,200)) . '...

  • '; + + } + $o .= '
'; + $o .= '
'; + $o .= ''; + } + return $o; + } + + + global $lang; + + $doctype = 'markdown'; + + $text = ''; + + if(argc() > 1) { + $path = ''; + for($x = 1; $x < argc(); $x ++) { + if(strlen($path)) + $path .= '/'; + $path .= argv($x); + } + $title = basename($path); + + $text = load_doc_file('doc/' . $path . '.md'); + \App::$page['title'] = t('Help:') . ' ' . ucwords(str_replace('-',' ',notags($title))); + + if(! $text) { + $text = load_doc_file('doc/' . $path . '.bb'); + if($text) + $doctype = 'bbcode'; + \App::$page['title'] = t('Help:') . ' ' . ucwords(str_replace('_',' ',notags($title))); + } + if(! $text) { + $text = load_doc_file('doc/' . $path . '.html'); + if($text) + $doctype = 'html'; + \App::$page['title'] = t('Help:') . ' ' . ucwords(str_replace('-',' ',notags($title))); + } + } + + if(! $text) { + $text = load_doc_file('doc/Site.md'); + \App::$page['title'] = t('Help'); + } + if(! $text) { + $doctype = 'bbcode'; + $text = load_doc_file('doc/main.bb'); + \App::$page['title'] = t('Help'); + } + + if(! strlen($text)) { + header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . t('Not Found')); + $tpl = get_markup_template("404.tpl"); + return replace_macros($tpl, array( + '$message' => t('Page not found.' ) + )); + } + + if($doctype === 'html') + $content = $text; + if($doctype === 'markdown') { + require_once('library/markdown.php'); + # escape #include tags + $text = preg_replace('/#include/ism', '%%include', $text); + $content = Markdown($text); + $content = preg_replace('/%%include/ism', '#include', $content); + } + if($doctype === 'bbcode') { + require_once('include/bbcode.php'); + $content = bbcode($text); + // bbcode retargets external content to new windows. This content is internal. + $content = str_replace(' target="_blank"','',$content); + } + + $content = preg_replace_callback("/#include (.*?)\;/ism", 'self::preg_callback_help_include', $content); + + return replace_macros(get_markup_template("help.tpl"), array( + '$title' => t('$Projectname Documentation'), + '$content' => translate_projectname($content) + )); + + } + + + private static function preg_callback_help_include($matches) { + + if($matches[1]) { + $include = str_replace($matches[0],load_doc_file($matches[1]),$matches[0]); + if(preg_match('/\.bb$/', $matches[1]) || preg_match('/\.txt$/', $matches[1])) { + require_once('include/bbcode.php'); + $include = bbcode($include); + $include = str_replace(' target="_blank"','',$include); + } + elseif(preg_match('/\.md$/', $matches[1])) { + require_once('library/markdown.php'); + $include = Markdown($include); + } + return $include; + } + + } + + +} diff --git a/Zotlabs/Module/Home.php b/Zotlabs/Module/Home.php new file mode 100644 index 000000000..79449c3b2 --- /dev/null +++ b/Zotlabs/Module/Home.php @@ -0,0 +1,102 @@ + 1 && argv(1) === 'splash') ? true : false); + + $channel = \App::get_channel(); + if(local_channel() && $channel && $channel['xchan_url'] && ! $splash) { + $dest = $channel['channel_startpage']; + if(! $dest) + $dest = get_pconfig(local_channel(),'system','startpage'); + if(! $dest) + $dest = get_config('system','startpage'); + if(! $dest) + $dest = z_root() . '/network'; + + goaway($dest); + } + + if(remote_channel() && (! $splash) && $_SESSION['atoken']) { + $r = q("select * from atoken where atoken_id = %d", + intval($_SESSION['atoken']) + ); + if($r) { + $x = channelx_by_n($r[0]['atoken_uid']); + if($x) { + goaway(z_root() . '/channel/' . $x['channel_address']); + } + } + } + + + if(get_account_id() && ! $splash) { + goaway(z_root() . '/new_channel'); + } + + } + + + function get($update = 0, $load = false) { + + $o = ''; + + + if(x($_SESSION,'theme')) + unset($_SESSION['theme']); + if(x($_SESSION,'mobile_theme')) + unset($_SESSION['mobile_theme']); + + $splash = ((argc() > 1 && argv(1) === 'splash') ? true : false); + + call_hooks('home_content',$o); + if($o) + return $o; + + $frontpage = get_config('system','frontpage'); + if($frontpage) { + if(strpos($frontpage,'include:') !== false) { + $file = trim(str_replace('include:' , '', $frontpage)); + if(file_exists($file)) { + \App::$page['template'] = 'full'; + \App::$page['title'] = t('$Projectname'); + $o .= file_get_contents($file); + return $o; + } + } + if(strpos($frontpage,'http') !== 0) + $frontpage = z_root() . '/' . $frontpage; + if(intval(get_config('system','mirror_frontpage'))) { + $o = '' . t('$Projectname') . ''; + echo $o; + killme(); + } + goaway($frontpage); + } + + + $sitename = get_config('system','sitename'); + if($sitename) + $o .= '

' . sprintf( t("Welcome to %s") ,$sitename) . '

'; + + $loginbox = get_config('system','login_on_homepage'); + if(intval($loginbox) || $loginbox === false) + $o .= login((\App::$config['system']['register_policy'] == REGISTER_CLOSED) ? 0 : 1); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Hostxrd.php b/Zotlabs/Module/Hostxrd.php new file mode 100644 index 000000000..1aae8da9e --- /dev/null +++ b/Zotlabs/Module/Hostxrd.php @@ -0,0 +1,24 @@ + \App::get_hostname(), + '$zroot' => z_root() + )); + $arr = array('xrd' => $x); + call_hooks('hostxrd',$arr); + + echo $arr['xrd']; + killme(); + } + +} diff --git a/Zotlabs/Module/Id.php b/Zotlabs/Module/Id.php new file mode 100644 index 000000000..e053bf99c --- /dev/null +++ b/Zotlabs/Module/Id.php @@ -0,0 +1,319 @@ + t('First Name'), + 'namePerson/last' => t('Last Name'), + 'namePerson/friendly' => t('Nickname'), + 'namePerson' => t('Full Name'), + 'contact/internet/email' => t('Email'), + 'contact/email' => t('Email'), + 'media/image/aspect11' => t('Profile Photo'), + 'media/image' => t('Profile Photo'), + 'media/image/default' => t('Profile Photo'), + 'media/image/16x16' => t('Profile Photo 16px'), + 'media/image/32x32' => t('Profile Photo 32px'), + 'media/image/48x48' => t('Profile Photo 48px'), + 'media/image/64x64' => t('Profile Photo 64px'), + 'media/image/80x80' => t('Profile Photo 80px'), + 'media/image/128x128' => t('Profile Photo 128px'), + 'timezone' => t('Timezone'), + 'contact/web/default' => t('Homepage URL'), + 'language/pref' => t('Language'), + 'birthDate/birthYear' => t('Birth Year'), + 'birthDate/birthMonth' => t('Birth Month'), + 'birthDate/birthday' => t('Birth Day'), + 'birthDate' => t('Birthdate'), + 'gender' => t('Gender'), +); + + +/** + * @brief Entrypoint for the OpenID implementation. + * + * @param App &$a + */ + +class Id extends \Zotlabs\Web\Controller { + + function init() { + + logger('id: ' . print_r($_REQUEST, true)); + + if(argc() > 1) { + $which = argv(1); + } else { + \App::$error = 404; + return; + } + + $profile = ''; + $channel = \App::get_channel(); + profile_load($which,$profile); + + $op = new MysqlProvider; + $op->server(); + } + + /** + * @brief Returns user data needed for OpenID. + * + * If no $handle is provided we will use local_channel() by default. + * + * @param string $handle (default null) + * @return boolean|array + */ + static public function getUserData($handle = null) { + if (! local_channel()) { + notice( t('Permission denied.') . EOL); + \App::$page['content'] = login(); + + return false; + } + + // logger('handle: ' . $handle); + + if ($handle) { + $r = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_address = '%s' limit 1", + dbesc($handle) + ); + } else { + $r = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d", + intval(local_channel()) + ); + } + + if (! r) + return false; + + $x = q("select * from account where account_id = %d limit 1", + intval($r[0]['channel_account_id']) + ); + if ($x) + $r[0]['email'] = $x[0]['account_email']; + + $p = q("select * from profile where is_default = 1 and uid = %d limit 1", + intval($r[0]['channel_account_id']) + ); + + $gender = ''; + if ($p[0]['gender'] == t('Male')) + $gender = 'M'; + if ($p[0]['gender'] == t('Female')) + $gender = 'F'; + + $r[0]['firstName'] = ((strpos($r[0]['channel_name'],' ')) ? substr($r[0]['channel_name'],0,strpos($r[0]['channel_name'],' ')) : $r[0]['channel_name']); + $r[0]['lastName'] = ((strpos($r[0]['channel_name'],' ')) ? substr($r[0]['channel_name'],strpos($r[0]['channel_name'],' ')+1) : ''); + $r[0]['namePerson'] = $r[0]['channel_name']; + $r[0]['pphoto'] = $r[0]['xchan_photo_l']; + $r[0]['pphoto16'] = z_root() . '/photo/profile/16/' . $r[0]['channel_id'] . '.jpg'; + $r[0]['pphoto32'] = z_root() . '/photo/profile/32/' . $r[0]['channel_id'] . '.jpg'; + $r[0]['pphoto48'] = z_root() . '/photo/profile/48/' . $r[0]['channel_id'] . '.jpg'; + $r[0]['pphoto64'] = z_root() . '/photo/profile/64/' . $r[0]['channel_id'] . '.jpg'; + $r[0]['pphoto80'] = z_root() . '/photo/profile/80/' . $r[0]['channel_id'] . '.jpg'; + $r[0]['pphoto128'] = z_root() . '/photo/profile/128/' . $r[0]['channel_id'] . '.jpg'; + $r[0]['timezone'] = $r[0]['channel_timezone']; + $r[0]['url'] = $r[0]['xchan_url']; + $r[0]['language'] = (($x[0]['account_language']) ? $x[0]['account_language'] : 'en'); + $r[0]['birthyear'] = ((intval(substr($p[0]['dob'],0,4))) ? intval(substr($p[0]['dob'],0,4)) : ''); + $r[0]['birthmonth'] = ((intval(substr($p[0]['dob'],5,2))) ? intval(substr($p[0]['dob'],5,2)) : ''); + $r[0]['birthday'] = ((intval(substr($p[0]['dob'],8,2))) ? intval(substr($p[0]['dob'],8,2)) : ''); + $r[0]['birthdate'] = (($r[0]['birthyear'] && $r[0]['birthmonth'] && $r[0]['birthday']) ? $p[0]['dob'] : ''); + $r[0]['gender'] = $gender; + + return $r[0]; + + /* + * if(isset($_POST['login'],$_POST['password'])) { + * $login = mysql_real_escape_string($_POST['login']); + * $password = sha1($_POST['password']); + * $q = mysql_query("SELECT * FROM Users WHERE login = '$login' AND password = '$password'"); + * if($data = mysql_fetch_assoc($q)) { + * return $data; + * } + * if($handle) { + * echo 'Wrong login/password.'; + * } + * } + * if($handle) { + * ?> + *
+ * + * Login:
+ * Password:
+ * + *
+ * 'firstName', + 'namePerson/last' => 'lastName', + 'namePerson/friendly' => 'channel_address', + 'namePerson' => 'namePerson', + 'contact/internet/email' => 'email', + 'contact/email' => 'email', + 'media/image/aspect11' => 'pphoto', + 'media/image' => 'pphoto', + 'media/image/default' => 'pphoto', + 'media/image/16x16' => 'pphoto16', + 'media/image/32x32' => 'pphoto32', + 'media/image/48x48' => 'pphoto48', + 'media/image/64x64' => 'pphoto64', + 'media/image/80x80' => 'pphoto80', + 'media/image/128x128' => 'pphoto128', + 'timezone' => 'timezone', + 'contact/web/default' => 'url', + 'language/pref' => 'language', + 'birthDate/birthYear' => 'birthyear', + 'birthDate/birthMonth' => 'birthmonth', + 'birthDate/birthday' => 'birthday', + 'birthDate' => 'birthdate', + 'gender' => 'gender', + ); + + function setup($identity, $realm, $assoc_handle, $attributes) { + global $attrMap; + + // logger('identity: ' . $identity); + // logger('realm: ' . $realm); + // logger('assoc_handle: ' . $assoc_handle); + // logger('attributes: ' . print_r($attributes,true)); + + $data = \Zotlabs\Module\Id::getUserData($assoc_handle); + + + /** @FIXME this needs to be a template with localised strings */ + + $o .= '
' + . '' + . '' + . '' + . "$realm wishes to authenticate you."; + if($attributes['required'] || $attributes['optional']) { + $o .= " It also requests following information (required fields marked with *):" + . '
    '; + + foreach($attributes['required'] as $attr) { + if(isset($this->attrMap[$attr])) { + $o .= '
  • ' + . ' ' + . $this->attrMap[$attr] . ' *
  • '; + } + } + + foreach($attributes['optional'] as $attr) { + if(isset($this->attrMap[$attr])) { + $o .= '
  • ' + . ' ' + . $this->attrMap[$attr] . '
  • '; + } + } + $o .= '
'; + } + $o .= '
' + . ' ' + . ' ' + . ' ' + . '
'; + + \App::$page['content'] .= $o; + } + + function checkid($realm, &$attributes) { + + logger('checkid: ' . $realm); + logger('checkid attrs: ' . print_r($attributes,true)); + + if(isset($_POST['cancel'])) { + $this->cancel(); + } + + $data = \Zotlabs\Module\Id::getUserData(); + if(! $data) { + return false; + } + + $q = get_pconfig(local_channel(), 'openid', $realm); + + $attrs = array(); + if($q) { + $attrs = $q; + } elseif(isset($_POST['attributes'])) { + $attrs = array_keys($_POST['attributes']); + } elseif(!isset($_POST['once']) && !isset($_POST['always'])) { + return false; + } + + $attributes = array(); + foreach($attrs as $attr) { + if(isset($this->attrFieldMap[$attr])) { + $attributes[$attr] = $data[$this->attrFieldMap[$attr]]; + } + } + + if(isset($_POST['always'])) { + set_pconfig(local_channel(),'openid',$realm,array_keys($attributes)); + } + + return z_root() . '/id/' . $data['channel_address']; + } + + function assoc_handle() { + logger('assoc_handle'); + $channel = \App::get_channel(); + + return z_root() . '/channel/' . $channel['channel_address']; + } + + function setAssoc($handle, $data) { + logger('setAssoc'); + $channel = channelx_by_nick(basename($handle)); + if($channel) + set_pconfig($channel['channel_id'],'openid','associate',$data); + } + + function getAssoc($handle) { + logger('getAssoc: ' . $handle); + + $channel = channelx_by_nick(basename($handle)); + if($channel) + return get_pconfig($channel['channel_id'], 'openid', 'associate'); + + return false; + } + + function delAssoc($handle) { + logger('delAssoc'); + $channel = channelx_by_nick(basename($handle)); + if($channel) + return del_pconfig($channel['channel_id'], 'openid', 'associate'); + } + } + diff --git a/Zotlabs/Module/Impel.php b/Zotlabs/Module/Impel.php new file mode 100644 index 000000000..735c311d0 --- /dev/null +++ b/Zotlabs/Module/Impel.php @@ -0,0 +1,199 @@ + false); + + if(! local_channel()) + json_return_and_die($ret); + + logger('impel: ' . print_r($_REQUEST,true), LOGGER_DATA); + + $elm = $_REQUEST['element']; + $x = base64url_decode($elm); + if(! $x) + json_return_and_die($ret); + + $j = json_decode($x,true); + if(! $j) + json_return_and_die($ret); + + $channel = \App::get_channel(); + + $arr = array(); + $is_menu = false; + + // a portable menu has its links rewritten with the local baseurl + $portable_menu = false; + + switch($j['type']) { + case 'webpage': + $arr['item_type'] = ITEM_TYPE_WEBPAGE; + $namespace = 'WEBPAGE'; + $installed_type = t('webpage'); + break; + case 'block': + $arr['item_type'] = ITEM_TYPE_BLOCK; + $namespace = 'BUILDBLOCK'; + $installed_type = t('block'); + break; + case 'layout': + $arr['item_type'] = ITEM_TYPE_PDL; + $namespace = 'PDL'; + $installed_type = t('layout'); + break; + case 'portable-menu': + $portable_menu = true; + // fall through + case 'menu': + $is_menu = true; + $installed_type = t('menu'); + break; + default: + logger('mod_impel: unrecognised element type' . print_r($j,true)); + break; + } + + if($is_menu) { + $m = array(); + $m['menu_channel_id'] = local_channel(); + $m['menu_name'] = $j['pagetitle']; + $m['menu_desc'] = $j['desc']; + if($j['created']) + $m['menu_created'] = datetime_convert($j['created']); + if($j['edited']) + $m['menu_edited'] = datetime_convert($j['edited']); + + $m['menu_flags'] = 0; + if($j['flags']) { + if(in_array('bookmark',$j['flags'])) + $m['menu_flags'] |= MENU_BOOKMARK; + if(in_array('system',$j['flags'])) + $m['menu_flags'] |= MENU_SYSTEM; + + } + + $menu_id = menu_create($m); + + if($menu_id) { + if(is_array($j['items'])) { + foreach($j['items'] as $it) { + $mitem = array(); + + $mitem['mitem_link'] = str_replace('[baseurl]',z_root(),$it['link']); + $mitem['mitem_desc'] = escape_tags($it['desc']); + $mitem['mitem_order'] = intval($it['order']); + if(is_array($it['flags'])) { + $mitem['mitem_flags'] = 0; + if(in_array('zid',$it['flags'])) + $mitem['mitem_flags'] |= MENU_ITEM_ZID; + if(in_array('new-window',$it['flags'])) + $mitem['mitem_flags'] |= MENU_ITEM_NEWWIN; + if(in_array('chatroom',$it['flags'])) + $mitem['mitem_flags'] |= MENU_ITEM_CHATROOM; + } + menu_add_item($menu_id,local_channel(),$mitem); + } + if($j['edited']) { + $x = q("update menu set menu_edited = '%s' where menu_id = %d and menu_channel_id = %d", + dbesc(datetime_convert('UTC','UTC',$j['edited'])), + intval($menu_id), + intval(local_channel()) + ); + } + } + $ret['success'] = true; + } + $x = $ret; + } + else { + $arr['uid'] = local_channel(); + $arr['aid'] = $channel['channel_account_id']; + $arr['title'] = $j['title']; + $arr['body'] = $j['body']; + $arr['term'] = $j['term']; + $arr['layout_mid'] = $j['layout_mid']; + $arr['created'] = datetime_convert('UTC','UTC', $j['created']); + $arr['edited'] = datetime_convert('UTC','UTC',$j['edited']); + $arr['owner_xchan'] = get_observer_hash(); + $arr['author_xchan'] = (($j['author_xchan']) ? $j['author_xchan'] : get_observer_hash()); + $arr['mimetype'] = (($j['mimetype']) ? $j['mimetype'] : 'text/bbcode'); + + if(! $j['mid']) + $j['mid'] = item_message_id(); + + $arr['mid'] = $arr['parent_mid'] = $j['mid']; + + + if($j['pagetitle']) { + require_once('library/urlify/URLify.php'); + $pagetitle = strtolower(\URLify::transliterate($j['pagetitle'])); + } + + // Verify ability to use html or php!!! + + $execflag = false; + + if($arr['mimetype'] === 'application/x-php') { + $z = q("select account_id, account_roles, channel_pageflags from account left join channel on channel_account_id = account_id where channel_id = %d limit 1", + intval(local_channel()) + ); + + if($z && (($z[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) || ($z[0]['channel_pageflags'] & PAGE_ALLOWCODE))) { + $execflag = true; + } + } + + $i = q("select id, edited, item_deleted from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['mid']), + intval(local_channel()) + ); + + \Zotlabs\Lib\IConfig::Set($arr,'system',$namespace,(($pagetitle) ? $pagetitle : substr($arr['mid'],0,16)),true); + + if($i) { + $arr['id'] = $i[0]['id']; + // don't update if it has the same timestamp as the original + if($arr['edited'] > $i[0]['edited']) + $x = item_store_update($arr,$execflag); + } + else { + if(($i) && (intval($i[0]['item_deleted']))) { + // was partially deleted already, finish it off + q("delete from item where mid = '%s' and uid = %d", + dbesc($arr['mid']), + intval(local_channel()) + ); + } + else + $x = item_store($arr,$execflag); + } + + if($x && $x['success']) { + $item_id = $x['item_id']; + } + } + + if($x['success']) { + $ret['success'] = true; + info( sprintf( t('%s element installed'), $installed_type)); + } + else { + notice( sprintf( t('%s element installation failed'), $installed_type)); + } + + //??? should perhaps return ret? + + json_return_and_die(true); + + } + +} diff --git a/Zotlabs/Module/Import.php b/Zotlabs/Module/Import.php new file mode 100644 index 000000000..d27f013b9 --- /dev/null +++ b/Zotlabs/Module/Import.php @@ -0,0 +1,567 @@ + $max_identities) { + notice( sprintf( t('Your service plan only allows %d channels.'), $max_identities) . EOL); + return; + } + } + + + $data = null; + $seize = ((x($_REQUEST,'make_primary')) ? intval($_REQUEST['make_primary']) : 0); + $import_posts = ((x($_REQUEST,'import_posts')) ? intval($_REQUEST['import_posts']) : 0); + $src = $_FILES['filename']['tmp_name']; + $filename = basename($_FILES['filename']['name']); + $filesize = intval($_FILES['filename']['size']); + $filetype = $_FILES['filename']['type']; + + $completed = ((array_key_exists('import_step',$_SESSION)) ? intval($_SESSION['import_step']) : 0); + if($completed) + logger('saved import step: ' . $_SESSION['import_step']); + + if($src) { + + // This is OS specific and could also fail if your tmpdir isn't very large + // mostly used for Diaspora which exports gzipped files. + + if(strpos($filename,'.gz')){ + @rename($src,$src . '.gz'); + @system('gunzip ' . escapeshellarg($src . '.gz')); + } + + if($filesize) { + $data = @file_get_contents($src); + } + unlink($src); + } + + if(! $src) { + $old_address = ((x($_REQUEST,'old_address')) ? $_REQUEST['old_address'] : ''); + if(! $old_address) { + logger('mod_import: nothing to import.'); + notice( t('Nothing to import.') . EOL); + return; + } + + $email = ((x($_REQUEST,'email')) ? $_REQUEST['email'] : ''); + $password = ((x($_REQUEST,'password')) ? $_REQUEST['password'] : ''); + + $channelname = substr($old_address,0,strpos($old_address,'@')); + $servername = substr($old_address,strpos($old_address,'@')+1); + + $scheme = 'https://'; + $api_path = '/api/red/channel/export/basic?f=&channel=' . $channelname; + if($import_posts) + $api_path .= '&posts=1'; + $binary = false; + $redirects = 0; + $opts = array('http_auth' => $email . ':' . $password); + $url = $scheme . $servername . $api_path; + $ret = z_fetch_url($url, $binary, $redirects, $opts); + if(! $ret['success']) + $ret = z_fetch_url('http://' . $servername . $api_path, $binary, $redirects, $opts); + if($ret['success']) + $data = $ret['body']; + else + notice( t('Unable to download data from old server') . EOL); + + } + + if(! $data) { + logger('mod_import: empty file.'); + notice( t('Imported file is empty.') . EOL); + return; + } + + $data = json_decode($data,true); + + // logger('import: data: ' . print_r($data,true)); + // print_r($data); + + + if(array_key_exists('user',$data) && array_key_exists('version',$data)) { + require_once('include/Import/import_diaspora.php'); + import_diaspora($data); + return; + } + + $moving = false; + + if(array_key_exists('compatibility',$data) && array_key_exists('database',$data['compatibility'])) { + $v1 = substr($data['compatibility']['database'],-4); + $v2 = substr(DB_UPDATE_VERSION,-4); + if($v2 > $v1) { + $t = sprintf( t('Warning: Database versions differ by %1$d updates.'), $v2 - $v1 ); + notice($t); + } + if(array_key_exists('server_role',$data['compatibility']) && $data['compatibility']['server_role'] == 'basic') + $moving = true; + } + + if($moving) + $seize = 1; + + // import channel + + $relocate = ((array_key_exists('relocate',$data)) ? $data['relocate'] : null); + + if(array_key_exists('channel',$data)) { + + if($completed < 1) { + $channel = import_channel($data['channel'], $account_id, $seize); + + } + else { + $r = q("select * from channel where channel_account_id = %d and channel_guid = '%s' limit 1", + intval($account_id), + dbesc($channel['channel_guid']) + ); + if($r) + $channel = $r[0]; + } + if(! $channel) { + logger('mod_import: channel not found. ', print_r($channel,true)); + notice( t('Cloned channel not found. Import failed.') . EOL); + return; + } + } + + if(! $channel) + $channel = \App::get_channel(); + + if(! $channel) { + logger('mod_import: channel not found. ', print_r($channel,true)); + notice( t('No channel. Import failed.') . EOL); + return; + } + + + if($completed < 2) { + if(is_array($data['config'])) { + import_config($channel,$data['config']); + } + + logger('import step 2'); + $_SESSION['import_step'] = 2; + } + + + if($completed < 3) { + + if($data['photo']) { + require_once('include/photo/photo_driver.php'); + import_channel_photo(base64url_decode($data['photo']['data']),$data['photo']['type'],$account_id,$channel['channel_id']); + } + + if(is_array($data['profile'])) + import_profiles($channel,$data['profile']); + + logger('import step 3'); + $_SESSION['import_step'] = 3; + } + + + if($completed < 4) { + + if(is_array($data['hubloc']) && (! $moving)) { + import_hublocs($channel,$data['hubloc'],$seize); + + } + logger('import step 4'); + $_SESSION['import_step'] = 4; + } + + if($completed < 5) { + // create new hubloc for the new channel at this site + + $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_network, hubloc_primary, + hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey ) + values ( '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s' )", + dbesc($channel['channel_guid']), + dbesc($channel['channel_guid_sig']), + dbesc($channel['channel_hash']), + dbesc($channel['channel_address'] . '@' . \App::get_hostname()), + dbesc('zot'), + intval(($seize) ? 1 : 0), + dbesc(z_root()), + dbesc(base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey']))), + dbesc(\App::get_hostname()), + dbesc(z_root() . '/post'), + dbesc(get_config('system','pubkey')) + ); + + // reset the original primary hubloc if it is being seized + + if($seize) { + $r = q("update hubloc set hubloc_primary = 0 where hubloc_primary = 1 and hubloc_hash = '%s' and hubloc_url != '%s' ", + dbesc($channel['channel_hash']), + dbesc(z_root()) + ); + } + logger('import step 5'); + $_SESSION['import_step'] = 5; + } + + + if($completed < 6) { + + // import xchans and contact photos + + if($seize) { + + // replace any existing xchan we may have on this site if we're seizing control + + $r = q("delete from xchan where xchan_hash = '%s'", + dbesc($channel['channel_hash']) + ); + + $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_hidden, xchan_orphan, xchan_censored, xchan_selfcensored, xchan_system, xchan_pubforum, xchan_deleted ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, %d, %d )", + dbesc($channel['channel_hash']), + dbesc($channel['channel_guid']), + dbesc($channel['channel_guid_sig']), + dbesc($channel['channel_pubkey']), + dbesc(z_root() . "/photo/profile/l/" . $channel['channel_id']), + dbesc(z_root() . "/photo/profile/m/" . $channel['channel_id']), + dbesc(z_root() . "/photo/profile/s/" . $channel['channel_id']), + dbesc($channel['channel_address'] . '@' . \App::get_hostname()), + dbesc(z_root() . '/channel/' . $channel['channel_address']), + dbesc(z_root() . '/follow?f=&url=%s'), + dbesc(z_root() . '/poco/' . $channel['channel_address']), + dbesc($channel['channel_name']), + dbesc('zot'), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + 0,0,0,0,0,0,0 + ); + } + logger('import step 6'); + $_SESSION['import_step'] = 6; + } + + if($completed < 7) { + + $xchans = $data['xchan']; + if($xchans) { + foreach($xchans as $xchan) { + + $hash = make_xchan_hash($xchan['xchan_guid'],$xchan['xchan_guid_sig']); + if($xchan['xchan_network'] === 'zot' && $hash !== $xchan['xchan_hash']) { + logger('forged xchan: ' . print_r($xchan,true)); + continue; + } + + if(! array_key_exists('xchan_hidden',$xchan)) { + $xchan['xchan_hidden'] = (($xchan['xchan_flags'] & 0x0001) ? 1 : 0); + $xchan['xchan_orphan'] = (($xchan['xchan_flags'] & 0x0002) ? 1 : 0); + $xchan['xchan_censored'] = (($xchan['xchan_flags'] & 0x0004) ? 1 : 0); + $xchan['xchan_selfcensored'] = (($xchan['xchan_flags'] & 0x0008) ? 1 : 0); + $xchan['xchan_system'] = (($xchan['xchan_flags'] & 0x0010) ? 1 : 0); + $xchan['xchan_pubforum'] = (($xchan['xchan_flags'] & 0x0020) ? 1 : 0); + $xchan['xchan_deleted'] = (($xchan['xchan_flags'] & 0x1000) ? 1 : 0); + } + + $r = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", + dbesc($xchan['xchan_hash']) + ); + if($r) + continue; + + dbesc_array($xchan); + + $r = dbq("INSERT INTO xchan (`" + . implode("`, `", array_keys($xchan)) + . "`) VALUES ('" + . implode("', '", array_values($xchan)) + . "')" ); + + + require_once('include/photo/photo_driver.php'); + $photos = import_xchan_photo($xchan['xchan_photo_l'],$xchan['xchan_hash']); + if($photos[4]) + $photodate = NULL_DATE; + else + $photodate = $xchan['xchan_photo_date']; + + $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s', xchan_photo_date = '%s' + where xchan_hash = '%s'", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($photodate), + dbesc($xchan['xchan_hash']) + ); + + } + } + logger('import step 7'); + $_SESSION['import_step'] = 7; + + } + + + + // FIXME - ensure we have an xchan if somebody is trying to pull a fast one + + if($completed < 8) { + $friends = 0; + $feeds = 0; + + // import contacts + $abooks = $data['abook']; + if($abooks) { + foreach($abooks as $abook) { + + $abook_copy = $abook; + + $abconfig = null; + if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) + $abconfig = $abook['abconfig']; + + unset($abook['abook_id']); + unset($abook['abook_rating']); + unset($abook['abook_rating_text']); + unset($abook['abconfig']); + unset($abook['abook_their_perms']); + unset($abook['abook_my_perms']); + + $abook['abook_account'] = $account_id; + $abook['abook_channel'] = $channel['channel_id']; + if(! array_key_exists('abook_blocked',$abook)) { + $abook['abook_blocked'] = (($abook['abook_flags'] & 0x0001 ) ? 1 : 0); + $abook['abook_ignored'] = (($abook['abook_flags'] & 0x0002 ) ? 1 : 0); + $abook['abook_hidden'] = (($abook['abook_flags'] & 0x0004 ) ? 1 : 0); + $abook['abook_archived'] = (($abook['abook_flags'] & 0x0008 ) ? 1 : 0); + $abook['abook_pending'] = (($abook['abook_flags'] & 0x0010 ) ? 1 : 0); + $abook['abook_unconnected'] = (($abook['abook_flags'] & 0x0020 ) ? 1 : 0); + $abook['abook_self'] = (($abook['abook_flags'] & 0x0080 ) ? 1 : 0); + $abook['abook_feed'] = (($abook['abook_flags'] & 0x0100 ) ? 1 : 0); + } + + if($abook['abook_self']) { + $role = get_pconfig($channel['channel_id'],'system','permissions_role'); + if(($role === 'forum') || ($abook['abook_my_perms'] & PERMS_W_TAGWALL)) { + q("update xchan set xchan_pubforum = 1 where xchan_hash = '%s' ", + dbesc($abook['abook_xchan']) + ); + } + } + else { + if($max_friends !== false && $friends > $max_friends) + continue; + if($max_feeds !== false && intval($abook['abook_feed']) && ($feeds > $max_feeds)) + continue; + } + + dbesc_array($abook); + $r = dbq("INSERT INTO abook (`" + . implode("`, `", array_keys($abook)) + . "`) VALUES ('" + . implode("', '", array_values($abook)) + . "')" ); + + $friends ++; + if(intval($abook['abook_feed'])) + $feeds ++; + + translate_abook_perms_inbound($channel,$abook_copy); + + if($abconfig) { + // @fixme does not handle sync of del_abconfig + foreach($abconfig as $abc) { + set_abconfig($channel['channel_id'],$abc['xchan'],$abc['cat'],$abc['k'],$abc['v']); + } + } + + + + } + } + logger('import step 8'); + $_SESSION['import_step'] = 8; + } + + + + if($completed < 9) { + $groups = $data['group']; + if($groups) { + $saved = array(); + foreach($groups as $group) { + $saved[$group['hash']] = array('old' => $group['id']); + if(array_key_exists('name',$group)) { + $group['gname'] = $group['name']; + unset($group['name']); + } + unset($group['id']); + $group['uid'] = $channel['channel_id']; + dbesc_array($group); + $r = dbq("INSERT INTO groups (`" + . implode("`, `", array_keys($group)) + . "`) VALUES ('" + . implode("', '", array_values($group)) + . "')" ); + } + $r = q("select * from `groups` where uid = %d", + intval($channel['channel_id']) + ); + if($r) { + foreach($r as $rr) { + $saved[$rr['hash']]['new'] = $rr['id']; + } + } + } + + + $group_members = $data['group_member']; + if($group_members) { + foreach($group_members as $group_member) { + unset($group_member['id']); + $group_member['uid'] = $channel['channel_id']; + foreach($saved as $x) { + if($x['old'] == $group_member['gid']) + $group_member['gid'] = $x['new']; + } + dbesc_array($group_member); + $r = dbq("INSERT INTO group_member (`" + . implode("`, `", array_keys($group_member)) + . "`) VALUES ('" + . implode("', '", array_values($group_member)) + . "')" ); + } + } + logger('import step 9'); + $_SESSION['import_step'] = 9; + } + + if(is_array($data['obj'])) + import_objs($channel,$data['obj']); + + if(is_array($data['likes'])) + import_likes($channel,$data['likes']); + + if(is_array($data['app'])) + import_apps($channel,$data['app']); + + if(is_array($data['chatroom'])) + import_chatrooms($channel,$data['chatroom']); + + if(is_array($data['conv'])) + import_conv($channel,$data['conv']); + + if(is_array($data['mail'])) + import_mail($channel,$data['mail']); + + if(is_array($data['event'])) + import_events($channel,$data['event']); + + if(is_array($data['event_item'])) + import_items($channel,$data['event_item'],false,$relocate); + + if(is_array($data['menu'])) + import_menus($channel,$data['menu']); + + $addon = array('channel' => $channel,'data' => $data); + call_hooks('import_channel',$addon); + + $saved_notification_flags = notifications_off($channel['channel_id']); + + if($import_posts && array_key_exists('item',$data) && $data['item']) + import_items($channel,$data['item'],false,$relocate); + + notifications_on($channel['channel_id'],$saved_notification_flags); + + + if(array_key_exists('item_id',$data) && $data['item_id']) + import_item_ids($channel,$data['item_id']); + + + // FIXME - ensure we have a self entry if somebody is trying to pull a fast one + + // send out refresh requests + // notify old server that it may no longer be primary. + + \Zotlabs\Daemon\Master::Summon(array('Notifier','location',$channel['channel_id'])); + + // This will indirectly perform a refresh_all *and* update the directory + + \Zotlabs\Daemon\Master::Summon(array('Directory', $channel['channel_id'])); + + + notice( t('Import completed.') . EOL); + + change_channel($channel['channel_id']); + + unset($_SESSION['import_step']); + goaway(z_root() . '/network' ); + + } + + + function post() { + + $account_id = get_account_id(); + if(! $account_id) + return; + + $this->import_account($account_id); + } + + function get() { + + if(! get_account_id()) { + notice( t('You must be logged in to use this feature.')); + return ''; + } + + $o = replace_macros(get_markup_template('channel_import.tpl'),array( + '$title' => t('Import Channel'), + '$desc' => t('Use this form to import an existing channel from a different server/hub. You may retrieve the channel identity from the old server/hub via the network or provide an export file.'), + '$label_filename' => t('File to Upload'), + '$choice' => t('Or provide the old server/hub details'), + '$label_old_address' => t('Your old identity address (xyz@example.com)'), + '$label_old_email' => t('Your old login email address'), + '$label_old_pass' => t('Your old login password'), + '$common' => t('For either option, please choose whether to make this hub your new primary address, or whether your old location should continue this role. You will be able to post from either location, but only one can be marked as the primary location for files, photos, and media.'), + '$label_import_primary' => t('Make this hub my primary location'), + '$label_import_posts' => t('Import existing posts if possible (experimental - limited by available memory'), + '$pleasewait' => t('This process may take several minutes to complete. Please submit the form only once and leave this page open until finished.'), + '$email' => '', + '$pass' => '', + '$submit' => t('Submit') + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Import_items.php b/Zotlabs/Module/Import_items.php new file mode 100644 index 000000000..f20cbfe7e --- /dev/null +++ b/Zotlabs/Module/Import_items.php @@ -0,0 +1,131 @@ + $email . ':' . $password); + $url = $scheme . $servername . $api_path; + $ret = z_fetch_url($url, $binary, $redirects, $opts); + if(! $ret['success']) + $ret = z_fetch_url('http://' . $servername . $api_path, $binary, $redirects, $opts); + if($ret['success']) + $data = $ret['body']; + else + notice( t('Unable to download data from old server') . EOL); + + } + + if(! $data) { + logger('mod_import: empty file.'); + notice( t('Imported file is empty.') . EOL); + return; + } + + $data = json_decode($data,true); + + // logger('import: data: ' . print_r($data,true)); + // print_r($data); + + if(! is_array($data)) + return; + + if(array_key_exists('compatibility',$data) && array_key_exists('database',$data['compatibility'])) { + $v1 = substr($data['compatibility']['database'],-4); + $v2 = substr(DB_UPDATE_VERSION,-4); + if($v2 > $v1) { + $t = sprintf( t('Warning: Database versions differ by %1$d updates.'), $v2 - $v1 ); + notice($t); + } + } + + $channel = \App::get_channel(); + + + if(array_key_exists('item',$data) && $data['item']) { + import_items($channel,$data['item'],false,((array_key_exists('relocate',$data)) ? $data['relocate'] : null)); + } + + if(array_key_exists('item_id',$data) && $data['item_id']) { + import_item_ids($channel,$data['item_id']); + } + + info( t('Import completed') . EOL); + return; + } + + + + + function get() { + + if(! local_channel()) { + notice( t('Permission denied') . EOL); + return login(); + } + + $o = replace_macros(get_markup_template('item_import.tpl'),array( + '$title' => t('Import Items'), + '$desc' => t('Use this form to import existing posts and content from an export file.'), + '$label_filename' => t('File to Upload'), + '$submit' => t('Submit') + )); + + return $o; + + } + + + +} diff --git a/Zotlabs/Module/Invite.php b/Zotlabs/Module/Invite.php new file mode 100644 index 000000000..3d7438484 --- /dev/null +++ b/Zotlabs/Module/Invite.php @@ -0,0 +1,152 @@ + $max_invites) { + notice( t('Total invitation limit exceeded.') . EOL); + return; + }; + + + $recips = ((x($_POST,'recipients')) ? explode("\n",$_POST['recipients']) : array()); + $message = ((x($_POST,'message')) ? notags(trim($_POST['message'])) : ''); + + $total = 0; + + if(get_config('system','invitation_only')) { + $invonly = true; + $x = get_pconfig(local_channel(),'system','invites_remaining'); + if((! $x) && (! is_site_admin())) + return; + } + + foreach($recips as $recip) { + + $recip = trim($recip); + if(! $recip) + continue; + + if(! valid_email($recip)) { + notice( sprintf( t('%s : Not a valid email address.'), $recip) . EOL); + continue; + } + + else + $nmessage = $message; + + $account = \App::get_account(); + + + $res = mail($recip, sprintf( t('Please join us on $Projectname'), \App::$config['sitename']), + $nmessage, + "From: " . $account['account_email'] . "\n" + . 'Content-type: text/plain; charset=UTF-8' . "\n" + . 'Content-transfer-encoding: 8bit' ); + + if($res) { + $total ++; + $current_invites ++; + set_pconfig(local_channel(),'system','sent_invites',$current_invites); + if($current_invites > $max_invites) { + notice( t('Invitation limit exceeded. Please contact your site administrator.') . EOL); + return; + } + } + else { + notice( sprintf( t('%s : Message delivery failed.'), $recip) . EOL); + } + + } + notice( sprintf( tt("%d message sent.", "%d messages sent.", $total) , $total) . EOL); + return; + } + + + function get() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + $tpl = get_markup_template('invite.tpl'); + $invonly = false; + + if(get_config('system','invitation_only')) { + $invonly = true; + $x = get_pconfig(local_channel(),'system','invites_remaining'); + if((! $x) && (! is_site_admin())) { + notice( t('You have no more invitations available') . EOL); + return ''; + } + } + + if($invonly && ($x || is_site_admin())) { + $invite_code = autoname(8) . rand(1000,9999); + $nmessage = str_replace('$invite_code',$invite_code,$message); + + $r = q("INSERT INTO `register` (`hash`,`created`) VALUES ('%s', '%s') ", + dbesc($invite_code), + dbesc(datetime_convert()) + ); + + if(! is_site_admin()) { + $x --; + if($x >= 0) + set_pconfig(local_channel(),'system','invites_remaining',$x); + else + return; + } + } + + $ob = \App::get_observer(); + if(! $ob) + return $o; + + $channel = \App::get_channel(); + + $o = replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("send_invite"), + '$invite' => t('Send invitations'), + '$addr_text' => t('Enter email addresses, one per line:'), + '$msg_text' => t('Your message:'), + '$default_message' => t('Please join my community on $Projectname.') . "\r\n" . "\r\n" + . $linktxt + . (($invonly) ? "\r\n" . "\r\n" . t('You will need to supply this invitation code:') . " " . $invite_code . "\r\n" . "\r\n" : '') + . t('1. Register at any $Projectname location (they are all inter-connected)') + . "\r\n" . "\r\n" . z_root() . '/register' + . "\r\n" . "\r\n" . t('2. Enter my $Projectname network address into the site searchbar.') + . "\r\n" . "\r\n" . $ob['xchan_addr'] . ' (' . t('or visit') . " " . z_root() . '/channel/' . $channel['channel_address'] . ')' + . "\r\n" . "\r\n" + . t('3. Click [Connect]') + . "\r\n" . "\r\n" , + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php new file mode 100644 index 000000000..2d0c1ba02 --- /dev/null +++ b/Zotlabs/Module/Item.php @@ -0,0 +1,1266 @@ + 1); + echo json_encode($json); + killme(); + } + + call_hooks('post_local_start', $_REQUEST); + + // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA); + + $api_source = ((x($_REQUEST,'api_source') && $_REQUEST['api_source']) ? true : false); + + $consensus = intval($_REQUEST['consensus']); + + // 'origin' (if non-zero) indicates that this network is where the message originated, + // for the purpose of relaying comments to other conversation members. + // If using the API from a device (leaf node) you must set origin to 1 (default) or leave unset. + // If the API is used from another network with its own distribution + // and deliveries, you may wish to set origin to 0 or false and allow the other + // network to relay comments. + + // If you are unsure, it is prudent (and important) to leave it unset. + + $origin = (($api_source && array_key_exists('origin',$_REQUEST)) ? intval($_REQUEST['origin']) : 1); + + // To represent message-ids on other networks - this will create an iconfig record + + $namespace = (($api_source && array_key_exists('namespace',$_REQUEST)) ? strip_tags($_REQUEST['namespace']) : ''); + $remote_id = (($api_source && array_key_exists('remote_id',$_REQUEST)) ? strip_tags($_REQUEST['remote_id']) : ''); + + $owner_hash = null; + + $message_id = ((x($_REQUEST,'message_id') && $api_source) ? strip_tags($_REQUEST['message_id']) : ''); + $created = ((x($_REQUEST,'created')) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['created']) : datetime_convert()); + $post_id = ((x($_REQUEST,'post_id')) ? intval($_REQUEST['post_id']) : 0); + $app = ((x($_REQUEST,'source')) ? strip_tags($_REQUEST['source']) : ''); + $return_path = ((x($_REQUEST,'return')) ? $_REQUEST['return'] : ''); + $preview = ((x($_REQUEST,'preview')) ? intval($_REQUEST['preview']) : 0); + $categories = ((x($_REQUEST,'category')) ? escape_tags($_REQUEST['category']) : ''); + $webpage = ((x($_REQUEST,'webpage')) ? intval($_REQUEST['webpage']) : 0); + $pagetitle = ((x($_REQUEST,'pagetitle')) ? escape_tags(urlencode($_REQUEST['pagetitle'])) : ''); + $layout_mid = ((x($_REQUEST,'layout_mid')) ? escape_tags($_REQUEST['layout_mid']): ''); + $plink = ((x($_REQUEST,'permalink')) ? escape_tags($_REQUEST['permalink']) : ''); + $obj_type = ((x($_REQUEST,'obj_type')) ? escape_tags($_REQUEST['obj_type']) : ACTIVITY_OBJ_NOTE); + + // allow API to bulk load a bunch of imported items with sending out a bunch of posts. + $nopush = ((x($_REQUEST,'nopush')) ? intval($_REQUEST['nopush']) : 0); + + /* + * Check service class limits + */ + if ($uid && !(x($_REQUEST,'parent')) && !(x($_REQUEST,'post_id'))) { + $ret = $this->item_check_service_class($uid,(($_REQUEST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false)); + if (!$ret['success']) { + notice( t($ret['message']) . EOL) ; + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + } + + if($pagetitle) { + require_once('library/urlify/URLify.php'); + $pagetitle = strtolower(\URLify::transliterate($pagetitle)); + } + + + $item_flags = $item_restrict = 0; + + $route = ''; + $parent_item = null; + $parent_contact = null; + $thr_parent = ''; + $parid = 0; + $r = false; + + if($parent || $parent_mid) { + + if(! x($_REQUEST,'type')) + $_REQUEST['type'] = 'net-comment'; + + if($obj_type == ACTIVITY_OBJ_POST) + $obj_type = ACTIVITY_OBJ_COMMENT; + + if($parent) { + $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", + intval($parent) + ); + } + elseif($parent_mid && $uid) { + // This is coming from an API source, and we are logged in + $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", + dbesc($parent_mid), + intval($uid) + ); + } + // if this isn't the real parent of the conversation, find it + if($r !== false && count($r)) { + $parid = $r[0]['parent']; + $parent_mid = $r[0]['mid']; + if($r[0]['id'] != $r[0]['parent']) { + $r = q("SELECT * FROM `item` WHERE `id` = `parent` AND `parent` = %d LIMIT 1", + intval($parid) + ); + } + } + + if(($r === false) || (! count($r))) { + notice( t('Unable to locate original post.') . EOL); + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + + // can_comment_on_post() needs info from the following xchan_query + // This may be from the discover tab which means we need to correct the effective uid + + xchan_query($r,true,(($r[0]['uid'] == local_channel()) ? 0 : local_channel())); + + $parent_item = $r[0]; + $parent = $r[0]['id']; + + // multi-level threading - preserve the info but re-parent to our single level threading + + $thr_parent = $parent_mid; + + $route = $parent_item['route']; + + } + + if(! $observer) + $observer = \App::get_observer(); + + if($parent) { + logger('mod_item: item_post parent=' . $parent); + $can_comment = false; + if((array_key_exists('owner',$parent_item)) && intval($parent_item['owner']['abook_self'])) + $can_comment = perm_is_allowed($profile_uid,$observer['xchan_hash'],'post_comments'); + else + $can_comment = can_comment_on_post($observer['xchan_hash'],$parent_item); + + if(! $can_comment) { + notice( t('Permission denied.') . EOL) ; + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + } + else { + if(! perm_is_allowed($profile_uid,$observer['xchan_hash'],($webpage) ? 'write_pages' : 'post_wall')) { + notice( t('Permission denied.') . EOL) ; + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + } + + + // is this an edited post? + + $orig_post = null; + + if($namespace && $remote_id) { + // It wasn't an internally generated post - see if we've got an item matching this remote service id + $i = q("select iid from iconfig where cat = 'system' and k = '%s' and v = '%s' limit 1", + dbesc($namespace), + dbesc($remote_id) + ); + if($i) + $post_id = $i[0]['iid']; + } + + $iconfig = null; + + if($post_id) { + $i = q("SELECT * FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", + intval($profile_uid), + intval($post_id) + ); + if(! count($i)) + killme(); + $orig_post = $i[0]; + $iconfig = q("select * from iconfig where iid = %d", + intval($post_id) + ); + } + + + if(! $channel) { + if($uid && $uid == $profile_uid) { + $channel = \App::get_channel(); + } + else { + // posting as yourself but not necessarily to a channel you control + $r = q("select * from channel left join account on channel_account_id = account_id where channel_id = %d LIMIT 1", + intval($profile_uid) + ); + if($r) + $channel = $r[0]; + } + } + + + if(! $channel) { + logger("mod_item: no channel."); + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + + $owner_xchan = null; + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($channel['channel_hash']) + ); + if($r && count($r)) { + $owner_xchan = $r[0]; + } + else { + logger("mod_item: no owner."); + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + + $walltowall = false; + $walltowall_comment = false; + + if($remote_xchan) + $observer = $remote_observer; + + if($observer) { + logger('mod_item: post accepted from ' . $observer['xchan_name'] . ' for ' . $owner_xchan['xchan_name'], LOGGER_DEBUG); + + // wall-to-wall detection. + // For top-level posts, if the author and owner are different it's a wall-to-wall + // For comments, We need to additionally look at the parent and see if it's a wall post that originated locally. + + if($observer['xchan_name'] != $owner_xchan['xchan_name']) { + if(($parent_item) && ($parent_item['item_wall'] && $parent_item['item_origin'])) { + $walltowall_comment = true; + $walltowall = true; + } + if(! $parent) { + $walltowall = true; + } + } + } + + $acl = new \Zotlabs\Access\AccessList($channel); + + $view_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_stream'); + $comment_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'post_comments'); + + $public_policy = ((x($_REQUEST,'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($view_policy,true)); + if($webpage) + $public_policy = ''; + if($public_policy) + $private = 1; + + if($orig_post) { + $private = 0; + // webpages are allowed to change ACLs after the fact. Normal conversation items aren't. + if($webpage) { + $acl->set_from_array($_REQUEST); + } + else { + $acl->set($orig_post); + $public_policy = $orig_post['public_policy']; + $private = $orig_post['item_private']; + } + + if($private || $public_policy || $acl->is_private()) + $private = 1; + + + $location = $orig_post['location']; + $coord = $orig_post['coord']; + $verb = $orig_post['verb']; + $app = $orig_post['app']; + $title = escape_tags(trim($_REQUEST['title'])); + $body = trim($_REQUEST['body']); + $item_flags = $orig_post['item_flags']; + + $item_origin = $orig_post['item_origin']; + $item_unseen = $orig_post['item_unseen']; + $item_starred = $orig_post['item_starred']; + $item_uplink = $orig_post['item_uplink']; + $item_consensus = $orig_post['item_consensus']; + $item_wall = $orig_post['item_wall']; + $item_thread_top = $orig_post['item_thread_top']; + $item_notshown = $orig_post['item_notshown']; + $item_nsfw = $orig_post['item_nsfw']; + $item_relay = $orig_post['item_relay']; + $item_mentionsme = $orig_post['item_mentionsme']; + $item_nocomment = $orig_post['item_nocomment']; + $item_obscured = $orig_post['item_obscured']; + $item_verified = $orig_post['item_verified']; + $item_retained = $orig_post['item_retained']; + $item_rss = $orig_post['item_rss']; + $item_deleted = $orig_post['item_deleted']; + $item_type = $orig_post['item_type']; + $item_hidden = $orig_post['item_hidden']; + $item_unpublished = $orig_post['item_unpublished']; + $item_delayed = $orig_post['item_delayed']; + $item_pending_remove = $orig_post['item_pending_remove']; + $item_blocked = $orig_post['item_blocked']; + + + + $postopts = $orig_post['postopts']; + $created = $orig_post['created']; + $mid = $orig_post['mid']; + $parent_mid = $orig_post['parent_mid']; + $plink = $orig_post['plink']; + + } + else { + if(! $walltowall) { + if((array_key_exists('contact_allow',$_REQUEST)) + || (array_key_exists('group_allow',$_REQUEST)) + || (array_key_exists('contact_deny',$_REQUEST)) + || (array_key_exists('group_deny',$_REQUEST))) { + $acl->set_from_array($_REQUEST); + } + elseif(! $api_source) { + + // if no ACL has been defined and we aren't using the API, the form + // didn't send us any parameters. This means there's no ACL or it has + // been reset to the default audience. + // If $api_source is set and there are no ACL parameters, we default + // to the channel permissions which were set in the ACL contructor. + + $acl->set(array('allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '')); + } + } + + + $location = notags(trim($_REQUEST['location'])); + $coord = notags(trim($_REQUEST['coord'])); + $verb = notags(trim($_REQUEST['verb'])); + $title = escape_tags(trim($_REQUEST['title'])); + $body = trim($_REQUEST['body']); + $body .= trim($_REQUEST['attachment']); + $postopts = ''; + + $private = intval($acl->is_private() || ($public_policy)); + + // If this is a comment, set the permissions from the parent. + + if($parent_item) { + $private = 0; + $acl->set($parent_item); + $private = intval($acl->is_private() || $parent_item['item_private']); + $public_policy = $parent_item['public_policy']; + $owner_hash = $parent_item['owner_xchan']; + } + + if(! strlen($body)) { + if($preview) + killme(); + info( t('Empty post discarded.') . EOL ); + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + } + + + $expires = NULL_DATE; + + if(feature_enabled($profile_uid,'content_expire')) { + if(x($_REQUEST,'expire')) { + $expires = datetime_convert(date_default_timezone_get(),'UTC', $_REQUEST['expire']); + if($expires <= datetime_convert()) + $expires = NULL_DATE; + } + } + + $mimetype = notags(trim($_REQUEST['mimetype'])); + if(! $mimetype) + $mimetype = 'text/bbcode'; + + if($preview) { + $body = z_input_filter($profile_uid,$body,$mimetype); + } + + + // Verify ability to use html or php!!! + + $execflag = false; + + if($mimetype !== 'text/bbcode') { + $z = q("select account_id, account_roles, channel_pageflags from account left join channel on channel_account_id = account_id where channel_id = %d limit 1", + intval($profile_uid) + ); + if($z && (($z[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE) || ($z[0]['channel_pageflags'] & PAGE_ALLOWCODE))) { + if($uid && (get_account_id() == $z[0]['account_id'])) { + $execflag = true; + } + else { + notice( t('Executable content type not permitted to this channel.') . EOL); + if(x($_REQUEST,'return')) + goaway(z_root() . "/" . $return_path ); + killme(); + } + } + } + + $gacl = $acl->get(); + $str_contact_allow = $gacl['allow_cid']; + $str_group_allow = $gacl['allow_gid']; + $str_contact_deny = $gacl['deny_cid']; + $str_group_deny = $gacl['deny_gid']; + + if($mimetype === 'text/bbcode') { + + require_once('include/text.php'); + + // Markdown doesn't work correctly. Do not re-enable unless you're willing to fix it and support it. + + // Sample that will probably give you grief - you must preserve the linebreaks + // and provide the correct markdown interpretation and you cannot allow unfiltered HTML + + // Markdown + // ======== + // + // **bold** abcde + // fghijkl + // *italic* + // + + // if($uid && $uid == $profile_uid && feature_enabled($uid,'markdown')) { + // require_once('include/bb2diaspora.php'); + // $body = escape_tags(trim($body)); + // $body = str_replace("\n",'
', $body); + // $body = preg_replace_callback('/\[share(.*?)\]/ism','\share_shield',$body); + // $body = diaspora2bb($body,true); + // $body = preg_replace_callback('/\[share(.*?)\]/ism','\share_unshield',$body); + // } + + // BBCODE alert: the following functions assume bbcode input + // and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.) + // we may need virtual or template classes to implement the possible alternatives + + // Work around doubled linefeeds in Tinymce 3.5b2 + // First figure out if it's a status post that would've been + // created using tinymce. Otherwise leave it alone. + + $plaintext = true; + + // $plaintext = ((feature_enabled($profile_uid,'richtext')) ? false : true); + // if((! $parent) && (! $api_source) && (! $plaintext)) { + // $body = fix_mce_lf($body); + // } + + + + // If we're sending a private top-level message with a single @-taggable channel as a recipient, @-tag it, if our pconfig is set. + + + if((! $parent) && (get_pconfig($profile_uid,'system','tagifonlyrecip')) && (substr_count($str_contact_allow,'<') == 1) && ($str_group_allow == '') && ($str_contact_deny == '') && ($str_group_deny == '')) { + $x = q("select abook_id, abconfig.v from abook left join abconfig on abook_xchan = abconfig.xchan and abook_channel = abconfig.chan and cat= 'their_perms' and abconfig.k = 'tag_deliver' and abconfig.v = 1 and abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc(str_replace(array('<','>'),array('',''),$str_contact_allow)), + intval($profile_uid) + ); + if($x) + $body .= "\n\n@group+" . $x[0]['abook_id'] . "\n"; + } + + /** + * fix naked links by passing through a callback to see if this is a hubzilla site + * (already known to us) which will get a zrl, otherwise link with url, add bookmark tag to both. + * First protect any url inside certain bbcode tags so we don't double link it. + */ + + + $body = preg_replace_callback('/\[code(.*?)\[\/(code)\]/ism','\red_escape_codeblock',$body); + $body = preg_replace_callback('/\[url(.*?)\[\/(url)\]/ism','\red_escape_codeblock',$body); + $body = preg_replace_callback('/\[zrl(.*?)\[\/(zrl)\]/ism','\red_escape_codeblock',$body); + + $body = preg_replace_callback("/([^\]\='".'"'."\/]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\+\,]+)/ism", '\red_zrl_callback', $body); + + $body = preg_replace_callback('/\[\$b64zrl(.*?)\[\/(zrl)\]/ism','\red_unescape_codeblock',$body); + $body = preg_replace_callback('/\[\$b64url(.*?)\[\/(url)\]/ism','\red_unescape_codeblock',$body); + $body = preg_replace_callback('/\[\$b64code(.*?)\[\/(code)\]/ism','\red_unescape_codeblock',$body); + + + // fix any img tags that should be zmg + + $body = preg_replace_callback('/\[img(.*?)\](.*?)\[\/img\]/ism','\red_zrlify_img_callback',$body); + + + $body = bb_translate_video($body); + + /** + * Fold multi-line [code] sequences + */ + + $body = preg_replace('/\[\/code\]\s*\[code\]/ism',"\n",$body); + + $body = scale_external_images($body,false); + + + // Look for tags and linkify them + $results = linkify_tags($a, $body, ($uid) ? $uid : $profile_uid); + + if($results) { + + // Set permissions based on tag replacements + set_linkified_perms($results, $str_contact_allow, $str_group_allow, $profile_uid, $parent_item, $private); + + $post_tags = array(); + foreach($results as $result) { + $success = $result['success']; + if($success['replaced']) { + $post_tags[] = array( + 'uid' => $profile_uid, + 'ttype' => $success['termtype'], + 'otype' => TERM_OBJ_POST, + 'term' => $success['term'], + 'url' => $success['url'] + ); + } + } + } + + + /** + * + * When a photo was uploaded into the message using the (profile wall) ajax + * uploader, The permissions are initially set to disallow anybody but the + * owner from seeing it. This is because the permissions may not yet have been + * set for the post. If it's private, the photo permissions should be set + * appropriately. But we didn't know the final permissions on the post until + * now. So now we'll look for links of uploaded photos and attachments that are in the + * post and set them to the same permissions as the post itself. + * + * If the post was end-to-end encrypted we can't find images and attachments in the body, + * use our media_str input instead which only contains these elements - but only do this + * when encrypted content exists because the photo/attachment may have been removed from + * the post and we should keep it private. If it's encrypted we have no way of knowing + * so we'll set the permissions regardless and realise that the media may not be + * referenced in the post. + * + * What is preventing us from being able to upload photos into comments is dealing with + * the photo and attachment permissions, since we don't always know who was in the + * distribution for the top level post. + * + * We might be able to provide this functionality with a lot of fiddling: + * - if the top level post is public (make the photo public) + * - if the top level post was written by us or a wall post that belongs to us (match the top level post) + * - if the top level post has privacy mentions, add those to the permissions. + * - otherwise disallow the photo *or* make the photo public. This is the part that gets messy. + */ + + if(! $preview) { + $this->fix_attached_photo_permissions($profile_uid,$owner_xchan['xchan_hash'],((strpos($body,'[/crypt]')) ? $_POST['media_str'] : $body),$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny); + + $this->fix_attached_file_permissions($channel,$observer['xchan_hash'],((strpos($body,'[/crypt]')) ? $_POST['media_str'] : $body),$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny); + + } + + + $attachments = ''; + $match = false; + + if(preg_match_all('/(\[attachment\](.*?)\[\/attachment\])/',$body,$match)) { + $attachments = array(); + $i = 0; + foreach($match[2] as $mtch) { + $attach_link = ''; + $hash = substr($mtch,0,strpos($mtch,',')); + $rev = intval(substr($mtch,strpos($mtch,','))); + $r = attach_by_hash_nodata($hash,$rev); + if($r['success']) { + $attachments[] = array( + 'href' => z_root() . '/attach/' . $r['data']['hash'], + 'length' => $r['data']['filesize'], + 'type' => $r['data']['filetype'], + 'title' => urlencode($r['data']['filename']), + 'revision' => $r['data']['revision'] + ); + } + $ext = substr($r['data']['filename'],strrpos($r['data']['filename'],'.')); + if(strpos($r['data']['filetype'],'audio/') !== false) + $attach_link = '[audio]' . z_root() . '/attach/' . $r['data']['hash'] . '/' . $r['data']['revision'] . (($ext) ? $ext : '') . '[/audio]'; + elseif(strpos($r['data']['filetype'],'video/') !== false) + $attach_link = '[video]' . z_root() . '/attach/' . $r['data']['hash'] . '/' . $r['data']['revision'] . (($ext) ? $ext : '') . '[/video]'; + $body = str_replace($match[1][$i],$attach_link,$body); + $i++; + } + } + + } + + // BBCODE end alert + + if(strlen($categories)) { + $cats = explode(',',$categories); + foreach($cats as $cat) { + $post_tags[] = array( + 'uid' => $profile_uid, + 'ttype' => TERM_CATEGORY, + 'otype' => TERM_OBJ_POST, + 'term' => trim($cat), + 'url' => $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat)) + ); + } + } + + if($orig_post) { + // preserve original tags + $t = q("select * from term where oid = %d and otype = %d and uid = %d and ttype in ( %d, %d, %d )", + intval($orig_post['id']), + intval(TERM_OBJ_POST), + intval($profile_uid), + intval(TERM_UNKNOWN), + intval(TERM_FILE), + intval(TERM_COMMUNITYTAG) + ); + if($t) { + foreach($t as $t1) { + $post_tags[] = array( + 'uid' => $profile_uid, + 'ttype' => $t1['type'], + 'otype' => TERM_OBJ_POST, + 'term' => $t1['term'], + 'url' => $t1['url'], + ); + } + } + } + + + $item_unseen = ((local_channel() != $profile_uid) ? 1 : 0); + $item_wall = (($post_type === 'wall' || $post_type === 'wall-comment') ? 1 : 0); + $item_origin = (($origin) ? 1 : 0); + $item_consensus = (($consensus) ? 1 : 0); + + + // determine if this is a wall post + + if($parent) { + $item_wall = $parent_item['item_wall']; + } + else { + if(! $webpage) { + $item_wall = 1; + } + } + + + if($moderated) + $item_blocked = ITEM_MODERATED; + + + if(! strlen($verb)) + $verb = ACTIVITY_POST ; + + $notify_type = (($parent) ? 'comment-new' : 'wall-new' ); + + if(! $mid) { + $mid = (($message_id) ? $message_id : item_message_id()); + } + if(! $parent_mid) { + $parent_mid = $mid; + } + + if($parent_item) + $parent_mid = $parent_item['mid']; + + // Fallback so that we alway have a thr_parent + + if(!$thr_parent) + $thr_parent = $mid; + + $datarray = array(); + + $item_thread_top = ((! $parent) ? 1 : 0); + + if ((! $plink) && ($item_thread_top)) { + $plink = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $mid; + } + + + + + + $datarray['aid'] = $channel['channel_account_id']; + $datarray['uid'] = $profile_uid; + + $datarray['owner_xchan'] = (($owner_hash) ? $owner_hash : $owner_xchan['xchan_hash']); + $datarray['author_xchan'] = $observer['xchan_hash']; + $datarray['created'] = $created; + $datarray['edited'] = (($orig_post) ? datetime_convert() : $created); + $datarray['expires'] = $expires; + $datarray['commented'] = (($orig_post) ? datetime_convert() : $created); + $datarray['received'] = (($orig_post) ? datetime_convert() : $created); + $datarray['changed'] = (($orig_post) ? datetime_convert() : $created); + $datarray['mid'] = $mid; + $datarray['parent_mid'] = $parent_mid; + $datarray['mimetype'] = $mimetype; + $datarray['title'] = $title; + $datarray['body'] = $body; + $datarray['app'] = $app; + $datarray['location'] = $location; + $datarray['coord'] = $coord; + $datarray['verb'] = $verb; + $datarray['obj_type'] = $obj_type; + $datarray['allow_cid'] = $str_contact_allow; + $datarray['allow_gid'] = $str_group_allow; + $datarray['deny_cid'] = $str_contact_deny; + $datarray['deny_gid'] = $str_group_deny; + $datarray['item_private'] = $private; + $datarray['item_wall'] = $item_wall; + $datarray['attach'] = $attachments; + $datarray['thr_parent'] = $thr_parent; + $datarray['postopts'] = $postopts; + $datarray['item_unseen'] = $item_unseen; + $datarray['item_wall'] = $item_wall; + $datarray['item_origin'] = $item_origin; + $datarray['item_type'] = $webpage; + $datarray['item_thread_top'] = $item_thread_top; + $datarray['item_unseen'] = $item_unseen; + $datarray['item_starred'] = $item_starred; + $datarray['item_uplink'] = $item_uplink; + $datarray['item_consensus'] = $item_consensus; + $datarray['item_notshown'] = $item_notshown; + $datarray['item_nsfw'] = $item_nsfw; + $datarray['item_relay'] = $item_relay; + $datarray['item_mentionsme'] = $item_mentionsme; + $datarray['item_nocomment'] = $item_nocomment; + $datarray['item_obscured'] = $item_obscured; + $datarray['item_verified'] = $item_verified; + $datarray['item_retained'] = $item_retained; + $datarray['item_rss'] = $item_rss; + $datarray['item_deleted'] = $item_deleted; + $datarray['item_hidden'] = $item_hidden; + $datarray['item_unpublished'] = $item_unpublished; + $datarray['item_delayed'] = $item_delayed; + $datarray['item_pending_remove'] = $item_pending_remove; + $datarray['item_blocked'] = $item_blocked; + + $datarray['layout_mid'] = $layout_mid; + $datarray['public_policy'] = $public_policy; + $datarray['comment_policy'] = map_scope($comment_policy); + $datarray['term'] = $post_tags; + $datarray['plink'] = $plink; + $datarray['route'] = $route; + + if($iconfig) + $datarray['iconfig'] = $iconfig; + + // preview mode - prepare the body for display and send it via json + + if($preview) { + require_once('include/conversation.php'); + + $datarray['owner'] = $owner_xchan; + $datarray['author'] = $observer; + $datarray['attach'] = json_encode($datarray['attach']); + $o = conversation($a,array($datarray),'search',false,'preview'); + // logger('preview: ' . $o, LOGGER_DEBUG); + echo json_encode(array('preview' => $o)); + killme(); + } + if($orig_post) + $datarray['edit'] = true; + + // suppress duplicates, *unless* you're editing an existing post. This could get picked up + // as a duplicate if you're editing it very soon after posting it initially and you edited + // some attribute besides the content, such as title or categories. + + if(feature_enabled($profile_uid,'suppress_duplicates') && (! $orig_post)) { + + $z = q("select created from item where uid = %d and created > %s - INTERVAL %s and body = '%s' limit 1", + intval($profile_uid), + db_utcnow(), + db_quoteinterval('2 MINUTE'), + dbesc($body) + ); + + if($z) { + $datarray['cancel'] = 1; + notice( t('Duplicate post suppressed.') . EOL); + logger('Duplicate post. Faking plugin cancel.'); + } + } + + call_hooks('post_local',$datarray); + + if(x($datarray,'cancel')) { + logger('mod_item: post cancelled by plugin or duplicate suppressed.'); + if($return_path) + goaway(z_root() . "/" . $return_path); + + $json = array('cancel' => 1); + $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; + echo json_encode($json); + killme(); + } + + + if(mb_strlen($datarray['title']) > 255) + $datarray['title'] = mb_substr($datarray['title'],0,255); + + if(array_key_exists('item_private',$datarray) && $datarray['item_private']) { + + $datarray['body'] = trim(z_input_filter($datarray['uid'],$datarray['body'],$datarray['mimetype'])); + + if($uid) { + if($channel['channel_hash'] === $datarray['author_xchan']) { + $datarray['sig'] = base64url_encode(rsa_sign($datarray['body'],$channel['channel_prvkey'])); + $datarray['item_verified'] = 1; + } + } + } + + if($webpage) { + Zlib\IConfig::Set($datarray,'system', webpage_to_namespace($webpage), + (($pagetitle) ? $pagetitle : substr($datarray['mid'],0,16)),true); + } + elseif($namespace) { + Zlib\IConfig::Set($datarray,'system', $namespace, + (($remote_id) ? $remote_id : substr($datarray['mid'],0,16)),true); + } + + + if($orig_post) { + $datarray['id'] = $post_id; + + $x = item_store_update($datarray,$execflag); + + if(! $parent) { + $r = q("select * from item where id = %d", + intval($post_id) + ); + if($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + build_sync_packet($profile_uid,array('item' => array(encode_item($sync_item[0],true)))); + } + } + if(! $nopush) + \Zotlabs\Daemon\Master::Summon(array('Notifier', 'edit_post', $post_id)); + + if((x($_REQUEST,'return')) && strlen($return_path)) { + logger('return: ' . $return_path); + goaway(z_root() . "/" . $return_path ); + } + killme(); + } + else + $post_id = 0; + + $post = item_store($datarray,$execflag); + + $post_id = $post['item_id']; + + if($post_id) { + logger('mod_item: saved item ' . $post_id); + + if($parent) { + + // only send comment notification if this is a wall-to-wall comment, + // otherwise it will happen during delivery + + if(($datarray['owner_xchan'] != $datarray['author_xchan']) && (intval($parent_item['item_wall']))) { + Zlib\Enotify::submit(array( + 'type' => NOTIFY_COMMENT, + 'from_xchan' => $datarray['author_xchan'], + 'to_xchan' => $datarray['owner_xchan'], + 'item' => $datarray, + 'link' => z_root() . '/display/' . $datarray['mid'], + 'verb' => ACTIVITY_POST, + 'otype' => 'item', + 'parent' => $parent, + 'parent_mid' => $parent_item['mid'] + )); + + } + } + else { + $parent = $post_id; + + if(($datarray['owner_xchan'] != $datarray['author_xchan']) && ($datarray['item_type'] == ITEM_TYPE_POST)) { + Zlib\Enotify::submit(array( + 'type' => NOTIFY_WALL, + 'from_xchan' => $datarray['author_xchan'], + 'to_xchan' => $datarray['owner_xchan'], + 'item' => $datarray, + 'link' => z_root() . '/display/' . $datarray['mid'], + 'verb' => ACTIVITY_POST, + 'otype' => 'item' + )); + } + + if($uid && $uid == $profile_uid && (is_item_normal($datarray))) { + q("update channel set channel_lastpost = '%s' where channel_id = %d", + dbesc(datetime_convert()), + intval($uid) + ); + } + } + + // photo comments turn the corresponding item visible to the profile wall + // This way we don't see every picture in your new photo album posted to your wall at once. + // They will show up as people comment on them. + + if(intval($parent_item['item_hidden'])) { + $r = q("UPDATE item SET item_hidden = 0 WHERE id = %d", + intval($parent_item['id']) + ); + } + } + else { + logger('mod_item: unable to retrieve post that was just stored.'); + notice( t('System error. Post not saved.') . EOL); + goaway(z_root() . "/" . $return_path ); + // NOTREACHED + } + + if(($parent) && ($parent != $post_id)) { + // Store the comment signature information in case we need to relay to Diaspora + //$ditem = $datarray; + //$ditem['author'] = $observer; + //store_diaspora_comment_sig($ditem,$channel,$parent_item, $post_id, (($walltowall_comment) ? 1 : 0)); + } + else { + $r = q("select * from item where id = %d", + intval($post_id) + ); + if($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + build_sync_packet($profile_uid,array('item' => array(encode_item($sync_item[0],true)))); + } + } + + $datarray['id'] = $post_id; + $datarray['llink'] = z_root() . '/display/' . $channel['channel_address'] . '/' . $post_id; + + call_hooks('post_local_end', $datarray); + + if(! $nopush) + \Zotlabs\Daemon\Master::Summon(array('Notifier', $notify_type, $post_id)); + + logger('post_complete'); + + // figure out how to return, depending on from whence we came + + if($api_source) + return $post; + + if($return_path) { + goaway(z_root() . "/" . $return_path); + } + + $json = array('success' => 1); + if(x($_REQUEST,'jsreload') && strlen($_REQUEST['jsreload'])) + $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; + + logger('post_json: ' . print_r($json,true), LOGGER_DEBUG); + + echo json_encode($json); + killme(); + // NOTREACHED + } + + + function get() { + + if((! local_channel()) && (! remote_channel())) + return; + + require_once('include/security.php'); + + if((argc() == 3) && (argv(1) === 'drop') && intval(argv(2))) { + + require_once('include/items.php'); + $i = q("select id, uid, author_xchan, owner_xchan, source_xchan, item_type from item where id = %d limit 1", + intval(argv(2)) + ); + + if($i) { + $can_delete = false; + $local_delete = false; + if(local_channel() && local_channel() == $i[0]['uid']) + $local_delete = true; + + $sys = get_sys_channel(); + if(is_site_admin() && $sys['channel_id'] == $i[0]['uid']) + $can_delete = true; + + $ob_hash = get_observer_hash(); + if($ob_hash && ($ob_hash === $i[0]['author_xchan'] || $ob_hash === $i[0]['owner_xchan'] || $ob_hash === $i[0]['source_xchan'])) + $can_delete = true; + + if(! ($can_delete || $local_delete)) { + notice( t('Permission denied.') . EOL); + return; + } + + // if this is a different page type or it's just a local delete + // but not by the item author or owner, do a simple deletion + + if(intval($i[0]['item_type']) || ($local_delete && (! $can_delete))) { + drop_item($i[0]['id']); + } + else { + // complex deletion that needs to propagate and be performed in phases + drop_item($i[0]['id'],true,DROPITEM_PHASE1); + tag_deliver($i[0]['uid'],$i[0]['id']); + } + } + } + } + + + function fix_attached_photo_permissions($uid,$xchan_hash,$body, + $str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny) { + + if(get_pconfig($uid,'system','force_public_uploads')) { + $str_contact_allow = $str_group_allow = $str_contact_deny = $str_group_deny = ''; + } + + $match = null; + // match img and zmg image links + if(preg_match_all("/\[[zi]mg(.*?)\](.*?)\[\/[zi]mg\]/",$body,$match)) { + $images = $match[2]; + if($images) { + foreach($images as $image) { + if(! stristr($image,z_root() . '/photo/')) + continue; + $image_uri = substr($image,strrpos($image,'/') + 1); + if(strpos($image_uri,'-') !== false) + $image_uri = substr($image_uri,0, strpos($image_uri,'-')); + if(strpos($image_uri,'.') !== false) + $image_uri = substr($image_uri,0, strpos($image_uri,'.')); + if(! strlen($image_uri)) + continue; + $srch = '<' . $xchan_hash . '>'; + + $r = q("select folder from attach where hash = '%s' and uid = %d limit 1", + dbesc($image_uri), + intval($uid) + ); + if($r && $r[0]['folder']) { + $f = q("select * from attach where hash = '%s' and is_dir = 1 and uid = %d limit 1", + dbesc($r[0]['folder']), + intval($uid) + ); + if(($f) && (($f[0]['allow_cid']) || ($f[0]['allow_gid']) || ($f[0]['deny_cid']) || ($f[0]['deny_gid']))) { + $str_contact_allow = $f[0]['allow_cid']; + $str_group_allow = $f[0]['allow_gid']; + $str_contact_deny = $f[0]['deny_cid']; + $str_group_deny = $f[0]['deny_gid']; + } + } + + $r = q("SELECT id FROM photo + WHERE allow_cid = '%s' AND allow_gid = '' AND deny_cid = '' AND deny_gid = '' + AND resource_id = '%s' AND uid = %d LIMIT 1", + dbesc($srch), + dbesc($image_uri), + intval($uid) + ); + + if($r) { + $r = q("UPDATE photo SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' + WHERE resource_id = '%s' AND uid = %d ", + dbesc($str_contact_allow), + dbesc($str_group_allow), + dbesc($str_contact_deny), + dbesc($str_group_deny), + dbesc($image_uri), + intval($uid) + ); + + // also update the linked item (which is probably invisible) + + $r = q("select id from item + WHERE allow_cid = '%s' AND allow_gid = '' AND deny_cid = '' AND deny_gid = '' + AND resource_id = '%s' and resource_type = 'photo' AND uid = %d LIMIT 1", + dbesc($srch), + dbesc($image_uri), + intval($uid) + ); + if($r) { + $private = (($str_contact_allow || $str_group_allow || $str_contact_deny || $str_group_deny) ? true : false); + + $r = q("UPDATE item SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d + WHERE id = %d AND uid = %d", + dbesc($str_contact_allow), + dbesc($str_group_allow), + dbesc($str_contact_deny), + dbesc($str_group_deny), + intval($private), + intval($r[0]['id']), + intval($uid) + ); + } + $r = q("select id from attach where hash = '%s' and uid = %d limit 1", + dbesc($image_uri), + intval($uid) + ); + if($r) { + q("update attach SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' + WHERE id = %d AND uid = %d", + dbesc($str_contact_allow), + dbesc($str_group_allow), + dbesc($str_contact_deny), + dbesc($str_group_deny), + intval($r[0]['id']), + intval($uid) + ); + } + } + } + } + } + } + + + function fix_attached_file_permissions($channel,$observer_hash,$body, + $str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny) { + + if(get_pconfig($channel['channel_id'],'system','force_public_uploads')) { + $str_contact_allow = $str_group_allow = $str_contact_deny = $str_group_deny = ''; + } + + $match = false; + + if(preg_match_all("/\[attachment\](.*?)\[\/attachment\]/",$body,$match)) { + $attaches = $match[1]; + if($attaches) { + foreach($attaches as $attach) { + $hash = substr($attach,0,strpos($attach,',')); + $rev = intval(substr($attach,strpos($attach,','))); + attach_store($channel,$observer_hash,$options = 'update', array( + 'hash' => $hash, + 'revision' => $rev, + 'allow_cid' => $str_contact_allow, + 'allow_gid' => $str_group_allow, + 'deny_cid' => $str_contact_deny, + 'deny_gid' => $str_group_deny + )); + } + } + } + } + + function item_check_service_class($channel_id,$iswebpage) { + $ret = array('success' => false, 'message' => ''); + + if ($iswebpage) { + $r = q("select count(i.id) as total from item i + right join channel c on (i.author_xchan=c.channel_hash and i.uid=c.channel_id ) + and i.parent=i.id and i.item_type = %d and i.item_deleted = 0 and i.uid= %d ", + intval(ITEM_TYPE_WEBPAGE), + intval($channel_id) + ); + } + else { + $r = q("select count(id) as total from item where parent = id and item_wall = 1 and uid = %d " . item_normal(), + intval($channel_id) + ); + } + + if(! $r) { + $ret['message'] = t('Unable to obtain post information from database.'); + return $ret; + } + + if (!$iswebpage) { + $max = engr_units_to_bytes(service_class_fetch($channel_id,'total_items')); + if(! service_class_allows($channel_id,'total_items',$r[0]['total'])) { + $result['message'] .= upgrade_message() . sprintf( t('You have reached your limit of %1$.0f top level posts.'),$max); + return $result; + } + } + else { + $max = engr_units_to_bytes(service_class_fetch($channel_id,'total_pages')); + if(! service_class_allows($channel_id,'total_pages',$r[0]['total'])) { + $result['message'] .= upgrade_message() . sprintf( t('You have reached your limit of %1$.0f webpages.'),$max); + return $result; + } + } + + $ret['success'] = true; + return $ret; + } + + +} diff --git a/Zotlabs/Module/Lang.php b/Zotlabs/Module/Lang.php new file mode 100644 index 000000000..69f10fe6d --- /dev/null +++ b/Zotlabs/Module/Lang.php @@ -0,0 +1,12 @@ + 1 && argv(1) === 'sys' && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + \App::$is_sys = true; + } + } + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + function get() { + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $which = argv(1); + + $_SESSION['return_url'] = \App::$query_string; + + $uid = local_channel(); + $owner = 0; + $channel = null; + $observer = \App::get_observer(); + + $channel = \App::get_channel(); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + $uid = $owner = intval($sys['channel_id']); + $channel = $sys; + $observer = $sys; + } + } + + if(! $owner) { + // Figure out who the page owner is. + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms($owner,$ob_hash); + + if(! $perms['write_pages']) { + notice( t('Permission denied.') . EOL); + return; + } + + // Block design features from visitors + + if((! $uid) || ($uid != $owner)) { + notice( t('Permission denied.') . EOL); + return; + } + + // Get the observer, check their permissions + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms($owner,$ob_hash); + + if(! $perms['write_pages']) { + notice( t('Permission denied.') . EOL); + return; + } + + // This feature is not exposed in redbasic ui since it is not clear why one would want to + // download a json encoded pdl file - we dont have a possibility to import it. + // Use the buildin share/install feature instead. + + if((argc() > 3) && (argv(2) === 'share') && (argv(3))) { + $r = q("select iconfig.v, iconfig.k, mimetype, title, body from iconfig + left join item on item.id = iconfig.iid + where uid = %d and mid = '%s' and iconfig.cat = 'system' and iconfig.k = 'PDL' order by iconfig.v asc", + intval($owner), + dbesc(argv(3)) + ); + if($r) { + header('Content-type: application/x-hubzilla-layout'); + header('Content-disposition: attachment; filename="' . $r[0]['sid'] . '.pdl"'); + echo json_encode($r); + killme(); + } + } + + // Create a status editor (for now - we'll need a WYSIWYG eventually) to create pages + // Nickname is set to the observers xchan, and profile_uid to the owners. + // This lets you post pages at other people's channels. + + $x = array( + 'webpage' => ITEM_TYPE_PDL, + 'is_owner' => true, + 'nickname' => \App::$profile['channel_address'], + 'showacl' => false, + 'hide_voting' => true, + 'hide_future' => true, + 'hide_expire' => true, + 'hide_location' => true, + 'hide_weblink' => true, + 'hide_attach' => true, + 'hide_preview' => true, + 'ptlabel' => t('Layout Name'), + 'profile_uid' => intval($owner), + 'expanded' => true, + 'placeholdertitle' => t('Layout Description (Optional)'), + 'novoting' => true, + 'bbco_autocomplete' => 'comanche' + ); + + if($_REQUEST['title']) + $x['title'] = $_REQUEST['title']; + if($_REQUEST['body']) + $x['body'] = $_REQUEST['body']; + if($_REQUEST['pagetitle']) + $x['pagetitle'] = $_REQUEST['pagetitle']; + + $editor = status_editor($a,$x); + + $r = q("select iconfig.iid, iconfig.v, mid, title, body, mimetype, created, edited, item_type from iconfig + left join item on iconfig.iid = item.id + where uid = %d and iconfig.cat = 'system' and iconfig.k = 'PDL' and item_type = %d order by item.created desc", + intval($owner), + intval(ITEM_TYPE_PDL) + ); + + $pages = null; + + if($r) { + $pages = array(); + foreach($r as $rr) { + $element_arr = array( + 'type' => 'layout', + 'title' => $rr['title'], + 'body' => $rr['body'], + 'created' => $rr['created'], + 'edited' => $rr['edited'], + 'mimetype' => $rr['mimetype'], + 'pagetitle' => $rr['sid'], + 'mid' => $rr['mid'] + ); + $pages[$rr['iid']][] = array( + 'url' => $rr['iid'], + 'title' => $rr['v'], + 'descr' => $rr['title'], + 'mid' => $rr['mid'], + 'created' => $rr['created'], + 'edited' => $rr['edited'], + 'bb_element' => '[element]' . base64url_encode(json_encode($element_arr)) . '[/element]' + ); + } + } + + //Build the base URL for edit links + $url = z_root() . '/editlayout/' . $which; + + $o .= replace_macros(get_markup_template('layoutlist.tpl'), array( + '$title' => t('Layouts'), + '$create' => t('Create'), + '$help' => array('text' => t('Help'), 'url' => 'help/comanche', 'title' => t('Comanche page description language help')), + '$editor' => $editor, + '$baseurl' => $url, + '$name' => t('Layout Name'), + '$descr' => t('Layout Description'), + '$created' => t('Created'), + '$edited' => t('Edited'), + '$edit' => t('Edit'), + '$share' => t('Share'), + '$download' => t('Download PDL file'), + '$pages' => $pages, + '$channel' => $which, + '$view' => t('View'), + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Like.php b/Zotlabs/Module/Like.php new file mode 100644 index 000000000..170349509 --- /dev/null +++ b/Zotlabs/Module/Like.php @@ -0,0 +1,546 @@ +' . t('Like/Dislike') . ''; + $o .= EOL . EOL; + + if(! $observer) { + $_SESSION['return_url'] = \App::$query_string; + $o .= t('This action is restricted to members.') . EOL; + $o .= t('Please login with your $Projectname ID or register as a new $Projectname member to continue.') . EOL; + return $o; + } + } + + $verb = notags(trim($_GET['verb'])); + + if(! $verb) + $verb = 'like'; + + switch($verb) { + case 'like': + case 'unlike': + $activity = ACTIVITY_LIKE; + break; + case 'dislike': + case 'undislike': + $activity = ACTIVITY_DISLIKE; + break; + case 'agree': + case 'unagree': + $activity = ACTIVITY_AGREE; + break; + case 'disagree': + case 'undisagree': + $activity = ACTIVITY_DISAGREE; + break; + case 'abstain': + case 'unabstain': + $activity = ACTIVITY_ABSTAIN; + break; + case 'attendyes': + case 'unattendyes': + $activity = ACTIVITY_ATTEND; + break; + case 'attendno': + case 'unattendno': + $activity = ACTIVITY_ATTENDNO; + break; + case 'attendmaybe': + case 'unattendmaybe': + $activity = ACTIVITY_ATTENDMAYBE; + break; + default: + return; + break; + } + + $extended_like = false; + $object = $target = null; + $post_type = ''; + $objtype = ''; + + if(argc() == 3) { + + if(! $observer) + killme(); + + $extended_like = true; + $obj_type = argv(1); + $obj_id = argv(2); + $public = true; + + if($obj_type == 'profile') { + $r = q("select * from profile where profile_guid = '%s' limit 1", + dbesc(argv(2)) + ); + if(! $r) + killme(); + $owner_uid = $r[0]['uid']; + if($r[0]['is_default']) + $public = true; + if(! $public) { + $d = q("select abook_xchan from abook where abook_profile = '%s' and abook_channel = %d", + dbesc($r[0]['profile_guid']), + intval($owner_uid) + ); + if(! $d) { + // forgery - illegal + if($interactive) { + notice( t('Invalid request.') . EOL); + return $o; + } + killme(); + } + // $d now contains a list of those who can see this profile - only send the status notification + // to them. + $allow_cid = $allow_gid = $deny_cid = $deny_gid = ''; + foreach($d as $dd) { + $allow_cid .= '<' . $dd['abook_xchan'] . '>'; + } + } + $post_type = t('channel'); + $objtype = ACTIVITY_OBJ_PROFILE; + + $profile = $r[0]; + } + elseif($obj_type == 'thing') { + + $r = q("select * from obj where obj_type = %d and obj_obj = '%s' limit 1", + intval(TERM_OBJ_THING), + dbesc(argv(2)) + ); + + if(! $r) { + if($interactive) { + notice( t('Invalid request.') . EOL); + return $o; + } + killme(); + } + + $owner_uid = $r[0]['obj_channel']; + + $allow_cid = $r[0]['allow_cid']; + $allow_gid = $r[0]['allow_gid']; + $deny_cid = $r[0]['deny_cid']; + $deny_gid = $r[0]['deny_gid']; + if($allow_cid || $allow_gid || $deny_cid || $deny_gid) + $public = false; + + $post_type = t('thing'); + $objtype = ACTIVITY_OBJ_PROFILE; + $tgttype = ACTIVITY_OBJ_THING; + + $links = array(); + $links[] = array('rel' => 'alternate', 'type' => 'text/html', + 'href' => z_root() . '/thing/' . $r[0]['obj_obj']); + if($r[0]['imgurl']) + $links[] = array('rel' => 'photo', 'href' => $r[0]['obj_imgurl']); + + $target = json_encode(array( + 'type' => $tgttype, + 'title' => $r[0]['obj_term'], + 'id' => z_root() . '/thing/' . $r[0]['obj_obj'], + 'link' => $links + )); + + $plink = '[zrl=' . z_root() . '/thing/' . $r[0]['obj_obj'] . ']' . $r[0]['obj_term'] . '[/zrl]'; + + } + + if(! ($owner_uid && $r)) { + if($interactive) { + notice( t('Invalid request.') . EOL); + return $o; + } + killme(); + } + + // The resultant activity is going to be a wall-to-wall post, so make sure this is allowed + + $perms = get_all_perms($owner_uid,$observer['xchan_hash']); + + if(! ($perms['post_like'] && $perms['view_profile'])) { + if($interactive) { + notice( t('Permission denied.') . EOL); + return $o; + } + killme(); + } + + $ch = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", + intval($owner_uid) + ); + if(! $ch) { + if($interactive) { + notice( t('Channel unavailable.') . EOL); + return $o; + } + killme(); + } + + if(! $plink) + $plink = '[zrl=' . z_root() . '/profile/' . $ch[0]['channel_address'] . ']' . $post_type . '[/zrl]'; + + $links = array(); + $links[] = array('rel' => 'alternate', 'type' => 'text/html', + 'href' => z_root() . '/profile/' . $ch[0]['channel_address']); + $links[] = array('rel' => 'photo', 'type' => $ch[0]['xchan_photo_mimetype'], + 'href' => $ch[0]['xchan_photo_l']); + + $object = json_encode(array( + 'type' => ACTIVITY_OBJ_PROFILE, + 'title' => $ch[0]['channel_name'], + 'id' => $ch[0]['xchan_url'] . '/' . $ch[0]['xchan_hash'], + 'link' => $links + )); + + + // second like of the same thing is "undo" for the first like + + $z = q("select * from likes where channel_id = %d and liker = '%s' and verb = '%s' and target_type = '%s' and target_id = '%s' limit 1", + intval($ch[0]['channel_id']), + dbesc($observer['xchan_hash']), + dbesc($activity), + dbesc(($tgttype)?$tgttype:$objtype), + dbesc($obj_id) + ); + + if($z) { + $z[0]['deleted'] = 1; + build_sync_packet($ch[0]['channel_id'],array('likes' => $z)); + + q("delete from likes where id = %d limit 1", + intval($z[0]['id']) + ); + if($z[0]['i_mid']) { + $r = q("select id from item where mid = '%s' and uid = %d limit 1", + dbesc($z[0]['i_mid']), + intval($ch[0]['channel_id']) + ); + if($r) + drop_item($r[0]['id'],false); + if($interactive) { + notice( t('Previous action reversed.') . EOL); + return $o; + } + } + killme(); + } + } + else { + + // this is used to like an item or comment + + $item_id = ((argc() == 2) ? notags(trim(argv(1))) : 0); + + logger('like: verb ' . $verb . ' item ' . $item_id, LOGGER_DEBUG); + + // get the item. Allow linked photos (which are normally hidden) to be liked + + $r = q("SELECT * FROM item WHERE id = %d + and item_type = 0 and item_deleted = 0 and item_unpublished = 0 + and item_delayed = 0 and item_pending_remove = 0 and item_blocked = 0 LIMIT 1", + intval($item_id) + ); + + if(! $item_id || (! $r)) { + logger('like: no item ' . $item_id); + killme(); + } + + + xchan_query($r,true,(($r[0]['uid'] == local_channel()) ? 0 : local_channel())); + + $item = $r[0]; + + $owner_uid = $r[0]['uid']; + $owner_aid = $r[0]['aid']; + + $can_comment = false; + if((array_key_exists('owner',$item)) && intval($item['owner']['abook_self'])) + $can_comment = perm_is_allowed($item['uid'],$observer['xchan_hash'],'post_comments'); + else + $can_comment = can_comment_on_post($observer['xchan_hash'],$item); + + if(! $can_comment) { + notice( t('Permission denied') . EOL); + killme(); + } + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($item['owner_xchan']) + ); + if($r) + $thread_owner = $r[0]; + else + killme(); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($item['author_xchan']) + ); + if($r) + $item_author = $r[0]; + else + killme(); + + + $verbs = " '".dbesc($activity)."' "; + + $multi_undo = false; + + // event participation and consensus items are essentially radio toggles. If you make a subsequent choice, + // we need to eradicate your first choice. + + if($activity === ACTIVITY_ATTEND || $activity === ACTIVITY_ATTENDNO || $activity === ACTIVITY_ATTENDMAYBE) { + $verbs = " '" . dbesc(ACTIVITY_ATTEND) . "','" . dbesc(ACTIVITY_ATTENDNO) . "','" . dbesc(ACTIVITY_ATTENDMAYBE) . "' "; + $multi_undo = 1; + } + if($activity === ACTIVITY_AGREE || $activity === ACTIVITY_DISAGREE || $activity === ACTIVITY_ABSTAIN) { + $verbs = " '" . dbesc(ACTIVITY_AGREE) . "','" . dbesc(ACTIVITY_DISAGREE) . "','" . dbesc(ACTIVITY_ABSTAIN) . "' "; + $multi_undo = true; + } + + $item_normal = item_normal(); + + $r = q("SELECT id, parent, uid, verb FROM item WHERE verb in ( $verbs ) $item_normal + AND author_xchan = '%s' AND thr_parent = '%s' and uid = %d ", + dbesc($observer['xchan_hash']), + dbesc($item['mid']), + intval($owner_uid) + ); + + if($r) { + // already liked it. Drop that item. + require_once('include/items.php'); + foreach($r as $rr) { + drop_item($rr['id'],false,DROPITEM_PHASE1); + // set the changed timestamp on the parent so we'll see the update without a page reload + $z = q("update item set changed = '%s' where id = %d and uid = %d", + dbesc(datetime_convert()), + intval($rr['parent']), + intval($rr['uid']) + ); + // Prior activity was a duplicate of the one we're submitting, just undo it; + // don't fall through and create another + if(activity_match($rr['verb'],$activity)) + $multi_undo = false; + + // drop_item was not done interactively, so we need to invoke the notifier + // in order to push the changes to connections + + \Zotlabs\Daemon\Master::Summon(array('Notifier','drop',$rr['id'])); + + } + + if($interactive) + return; + + if(! $multi_undo) + killme(); + } + } + + $mid = item_message_id(); + + $arr = array(); + + if($extended_like) { + $arr['item_thread_top'] = 1; + $arr['item_origin'] = 1; + $arr['item_wall'] = 1; + } + else { + $post_type = (($item['resource_type'] === 'photo') ? t('photo') : t('status')); + if($item['obj_type'] === ACTIVITY_OBJ_EVENT) + $post_type = t('event'); + + $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $item['plink'])); + $objtype = (($item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); + + $body = $item['body']; + + $object = json_encode(array( + 'type' => $objtype, + 'id' => $item['mid'], + 'parent' => (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_mid']), + 'link' => $links, + 'title' => $item['title'], + 'content' => $item['body'], + 'created' => $item['created'], + 'edited' => $item['edited'], + 'author' => array( + 'name' => $item_author['xchan_name'], + 'address' => $item_author['xchan_addr'], + 'guid' => $item_author['xchan_guid'], + 'guid_sig' => $item_author['xchan_guid_sig'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']), + array('rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m'])), + ), + )); + + if(! intval($item['item_thread_top'])) + $post_type = 'comment'; + + $arr['item_origin'] = 1; + $arr['item_notshown'] = 1; + + if(intval($item['item_wall'])) + $arr['item_wall'] = 1; + + // if this was a linked photo and was hidden, unhide it. + + if(intval($item['item_hidden'])) { + $r = q("update item set item_hidden = 0 where id = %d", + intval($item['id']) + ); + } + + } + + if($verb === 'like') + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + if($verb === 'dislike') + $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); + if($verb === 'agree') + $bodyverb = t('%1$s agrees with %2$s\'s %3$s'); + if($verb === 'disagree') + $bodyverb = t('%1$s doesn\'t agree with %2$s\'s %3$s'); + if($verb === 'abstain') + $bodyverb = t('%1$s abstains from a decision on %2$s\'s %3$s'); + if($verb === 'attendyes') + $bodyverb = t('%1$s is attending %2$s\'s %3$s'); + if($verb === 'attendno') + $bodyverb = t('%1$s is not attending %2$s\'s %3$s'); + if($verb === 'attendmaybe') + $bodyverb = t('%1$s may attend %2$s\'s %3$s'); + + if(! isset($bodyverb)) + killme(); + + + + if($extended_like) { + $ulink = '[zrl=' . $ch[0]['xchan_url'] . ']' . $ch[0]['xchan_name'] . '[/zrl]'; + $alink = '[zrl=' . $observer['xchan_url'] . ']' . $observer['xchan_name'] . '[/zrl]'; + $private = (($public) ? 0 : 1); + } + else { + $arr['parent'] = $item['id']; + $arr['thr_parent'] = $item['mid']; + $ulink = '[zrl=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/zrl]'; + $alink = '[zrl=' . $observer['xchan_url'] . ']' . $observer['xchan_name'] . '[/zrl]'; + $plink = '[zrl=' . z_root() . '/display/' . $item['mid'] . ']' . $post_type . '[/zrl]'; + $allow_cid = $item['allow_cid']; + $allow_gid = $item['allow_gid']; + $deny_cid = $item['deny_cid']; + $deny_gid = $item['deny_gid']; + $private = $item['private']; + + } + + + $arr['mid'] = $mid; + $arr['aid'] = (($extended_like) ? $ch[0]['channel_account_id'] : $owner_aid); + $arr['uid'] = $owner_uid; + $arr['item_flags'] = $item_flags; + $arr['item_wall'] = $item_wall; + $arr['parent_mid'] = (($extended_like) ? $mid : $item['mid']); + $arr['owner_xchan'] = (($extended_like) ? $ch[0]['xchan_hash'] : $thread_owner['xchan_hash']); + $arr['author_xchan'] = $observer['xchan_hash']; + + + $arr['body'] = sprintf( $bodyverb, $alink, $ulink, $plink ); + if($obj_type === 'thing' && $r[0]['imgurl']) { + $arr['body'] .= "\n\n[zmg=80x80]" . $r[0]['imgurl'] . '[/zmg]'; + } + if($obj_type === 'profile') { + if($public) { + $arr['body'] .= "\n\n" . '[embed]' . z_root() . '/profile/' . $ch[0]['channel_address'] . '[/embed]'; + } + else + $arr['body'] .= "\n\n[zmg=80x80]" . $profile['thumb'] . '[/zmg]'; + } + + + $arr['verb'] = $activity; + $arr['obj_type'] = $objtype; + $arr['obj'] = $object; + + if($target) { + $arr['tgt_type'] = $tgttype; + $arr['target'] = $target; + } + + $arr['allow_cid'] = $allow_cid; + $arr['allow_gid'] = $allow_gid; + $arr['deny_cid'] = $deny_cid; + $arr['deny_gid'] = $deny_gid; + $arr['item_private'] = $private; + + + $post = item_store($arr); + $post_id = $post['item_id']; + + $arr['id'] = $post_id; + + call_hooks('post_local_end', $arr); + + + if($extended_like) { + $r = q("insert into likes (channel_id,liker,likee,iid,i_mid,verb,target_type,target_id,target) values (%d,'%s','%s',%d,'%s','%s','%s','%s','%s')", + intval($ch[0]['channel_id']), + dbesc($observer['xchan_hash']), + dbesc($ch[0]['channel_hash']), + intval($post_id), + dbesc($mid), + dbesc($activity), + dbesc(($tgttype)? $tgttype : $objtype), + dbesc($obj_id), + dbesc(($target) ? $target : $object) + ); + $r = q("select * from likes where liker = '%s' and likee = '%s' and i_mid = '%s' and verb = '%s' and target_type = '%s' and target_id = '%s' ", + dbesc($observer['xchan_hash']), + dbesc($ch[0]['channel_hash']), + dbesc($mid), + dbesc($activity), + dbesc(($tgttype)? $tgttype : $objtype), + dbesc($obj_id) + ); + if($r) + build_sync_packet($ch[0]['channel_id'],array('likes' => $r)); + + } + + + \Zotlabs\Daemon\Master::Summon(array('Notifier','like',$post_id)); + + if($interactive) { + notice( t('Action completed.') . EOL); + $o .= t('Thank you.'); + return $o; + } + + killme(); + } + + + +} diff --git a/Zotlabs/Module/Linkinfo.php b/Zotlabs/Module/Linkinfo.php new file mode 100644 index 000000000..e1a3a6abe --- /dev/null +++ b/Zotlabs/Module/Linkinfo.php @@ -0,0 +1,381 @@ + true, 'nobody' => true)); + if($result['success']) { + $hdrs=array(); + $h = explode("\n",$result['header']); + foreach ($h as $l) { + list($k,$v) = array_map("trim", explode(":", trim($l), 2)); + $hdrs[$k] = $v; + } + if (array_key_exists('Content-Type', $hdrs)) + $type = $hdrs['Content-Type']; + if($type) { + $zrl = is_matrix_url($url); + if(stripos($type,'image/') !== false) { + if($zrl) + echo $br . '[zmg]' . $url . '[/zmg]' . $br; + else + echo $br . '[img]' . $url . '[/img]' . $br; + killme(); + } + if(stripos($type,'video/') !== false) { + if($zrl) + echo $br . '[zvideo]' . $url . '[/zvideo]' . $br; + else + echo $br . '[video]' . $url . '[/video]' . $br; + killme(); + } + if(stripos($type,'audio/') !== false) { + if($zrl) + echo $br . '[zaudio]' . $url . '[/zaudio]' . $br; + else + echo $br . '[audio]' . $url . '[/audio]' . $br; + killme(); + } + } + } + + $template = $br . '#^[url=%s]%s[/url]%s' . $br; + + $arr = array('url' => $url, 'text' => ''); + + call_hooks('parse_link', $arr); + + if(strlen($arr['text'])) { + echo $arr['text']; + killme(); + } + + $x = oembed_process($url); + if($x) { + echo $x; + killme(); + } + + if($url && $title && $text) { + + $text = $br . '[quote]' . trim($text) . '[/quote]' . $br; + + $title = str_replace(array("\r","\n"),array('',''),$title); + + $result = sprintf($template,$url,($title) ? $title : $url,$text) . $str_tags; + + logger('linkinfo (unparsed): returns: ' . $result); + + echo $result; + killme(); + } + + $siteinfo = self::parseurl_getsiteinfo($url); + + // If this is a Red site, use zrl rather than url so they get zids sent to them by default + + if( x($siteinfo,'generator') && (strpos($siteinfo['generator'], \Zotlabs\Lib\System::get_platform_name() . ' ') === 0)) + $template = str_replace('url','zrl',$template); + + if($siteinfo["title"] == "") { + echo sprintf($template,$url,$url,'') . $str_tags; + killme(); + } else { + $text = $siteinfo["text"]; + $title = $siteinfo["title"]; + } + + $image = ""; + + if(sizeof($siteinfo["images"]) > 0){ + /* Execute below code only if image is present in siteinfo */ + + $total_images = 0; + $max_images = get_config('system','max_bookmark_images'); + if($max_images === false) + $max_images = 2; + else + $max_images = intval($max_images); + + foreach ($siteinfo["images"] as $imagedata) { + if ($url) { + $image .= sprintf('[url=%s]', $url); + } + $image .= '[img='.$imagedata["width"].'x'.$imagedata["height"].']'.$imagedata["src"].'[/img]'; + if ($url) { + $image .= '[/url]'; + } + $image .= "\n"; + $total_images ++; + if($max_images && $max_images >= $total_images) + break; + } + } + + if(strlen($text)) { + $text = $br.'[quote]'.trim($text).'[/quote]'.$br ; + } + + if($image) { + $text = $br.$br.$image.$text; + } + $title = str_replace(array("\r","\n"),array('',''),$title); + + $result = sprintf($template,$url,($title) ? $title : $url,$text) . $str_tags; + + logger('linkinfo: returns: ' . $result, LOGGER_DEBUG); + + echo trim($result); + killme(); + + } + + + public static function deletexnode(&$doc, $node) { + $xpath = new \DomXPath($doc); + $list = $xpath->query("//".$node); + foreach ($list as $child) + $child->parentNode->removeChild($child); + } + + public static function completeurl($url, $scheme) { + $urlarr = parse_url($url); + + if (isset($urlarr["scheme"])) + return($url); + + $schemearr = parse_url($scheme); + + $complete = $schemearr["scheme"]."://".$schemearr["host"]; + + if ($schemearr["port"] != "") + $complete .= ":".$schemearr["port"]; + + if(strpos($urlarr['path'],'/') !== 0) + $complete .= '/'; + + $complete .= $urlarr["path"]; + + if ($urlarr["query"] != "") + $complete .= "?".$urlarr["query"]; + + if ($urlarr["fragment"] != "") + $complete .= "#".$urlarr["fragment"]; + + return($complete); + } + + + public static function parseurl_getsiteinfo($url) { + $siteinfo = array(); + + + $result = z_fetch_url($url,false,0,array('novalidate' => true)); + if(! $result['success']) + return $siteinfo; + + $header = $result['header']; + $body = $result['body']; + + $body = mb_convert_encoding($body, 'UTF-8', 'UTF-8'); + $body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8"); + + $doc = new \DOMDocument(); + @$doc->loadHTML($body); + + self::deletexnode($doc, 'style'); + self::deletexnode($doc, 'script'); + self::deletexnode($doc, 'option'); + self::deletexnode($doc, 'h1'); + self::deletexnode($doc, 'h2'); + self::deletexnode($doc, 'h3'); + self::deletexnode($doc, 'h4'); + self::deletexnode($doc, 'h5'); + self::deletexnode($doc, 'h6'); + self::deletexnode($doc, 'ol'); + self::deletexnode($doc, 'ul'); + + $xpath = new \DomXPath($doc); + + //$list = $xpath->query("head/title"); + $list = $xpath->query("//title"); + foreach ($list as $node) + $siteinfo["title"] = html_entity_decode($node->nodeValue, ENT_QUOTES, "UTF-8"); + + //$list = $xpath->query("head/meta[@name]"); + $list = $xpath->query("//meta[@name]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) + foreach ($node->attributes as $attribute) + $attr[$attribute->name] = $attribute->value; + + $attr["content"] = html_entity_decode($attr["content"], ENT_QUOTES, "UTF-8"); + + switch (strtolower($attr["name"])) { + case 'generator': + $siteinfo['generator'] = $attr['content']; + break; + case "fulltitle": + $siteinfo["title"] = $attr["content"]; + break; + case "description": + $siteinfo["text"] = $attr["content"]; + break; + case "dc.title": + $siteinfo["title"] = $attr["content"]; + break; + case "dc.description": + $siteinfo["text"] = $attr["content"]; + break; + } + } + + //$list = $xpath->query("head/meta[@property]"); + $list = $xpath->query("//meta[@property]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) + foreach ($node->attributes as $attribute) + $attr[$attribute->name] = $attribute->value; + + $attr["content"] = html_entity_decode($attr["content"], ENT_QUOTES, "UTF-8"); + + switch (strtolower($attr["property"])) { + case "og:image": + $siteinfo["image"] = $attr["content"]; + break; + case "og:title": + $siteinfo["title"] = $attr["content"]; + break; + case "og:description": + $siteinfo["text"] = $attr["content"]; + break; + } + } + + if ($siteinfo["image"] == "") { + $list = $xpath->query("//img[@src]"); + foreach ($list as $node) { + $attr = array(); + if ($node->attributes->length) + foreach ($node->attributes as $attribute) + $attr[$attribute->name] = $attribute->value; + + $src = self::completeurl($attr["src"], $url); + $photodata = @getimagesize($src); + + if (($photodata) && ($photodata[0] > 150) and ($photodata[1] > 150)) { + if ($photodata[0] > 300) { + $photodata[1] = round($photodata[1] * (300 / $photodata[0])); + $photodata[0] = 300; + } + if ($photodata[1] > 300) { + $photodata[0] = round($photodata[0] * (300 / $photodata[1])); + $photodata[1] = 300; + } + $siteinfo["images"][] = array("src"=>$src, + "width"=>$photodata[0], + "height"=>$photodata[1]); + } + + } + } else { + $src = self::completeurl($siteinfo["image"], $url); + + unset($siteinfo["image"]); + + $photodata = @getimagesize($src); + + if (($photodata) && ($photodata[0] > 10) and ($photodata[1] > 10)) + $siteinfo["images"][] = array("src"=>$src, + "width"=>$photodata[0], + "height"=>$photodata[1]); + } + + if ($siteinfo["text"] == "") { + $text = ""; + + $list = $xpath->query("//div[@class='article']"); + foreach ($list as $node) + if (strlen($node->nodeValue) > 40) + $text .= " ".trim($node->nodeValue); + + if ($text == "") { + $list = $xpath->query("//div[@class='content']"); + foreach ($list as $node) + if (strlen($node->nodeValue) > 40) + $text .= " ".trim($node->nodeValue); + } + + // If none text was found then take the paragraph content + if ($text == "") { + $list = $xpath->query("//p"); + foreach ($list as $node) + if (strlen($node->nodeValue) > 40) + $text .= " ".trim($node->nodeValue); + } + + if ($text != "") { + $text = trim(str_replace(array("\n", "\r"), array(" ", " "), $text)); + + while (strpos($text, " ")) + $text = trim(str_replace(" ", " ", $text)); + + $siteinfo["text"] = html_entity_decode(substr($text,0,350), ENT_QUOTES, "UTF-8").'...'; + } + } + + return($siteinfo); + } + + + private static function arr_add_hashes(&$item,$k) { + $item = '#' . $item; + } + + + + +} diff --git a/Zotlabs/Module/Lockview.php b/Zotlabs/Module/Lockview.php new file mode 100644 index 000000000..d86a3c1d8 --- /dev/null +++ b/Zotlabs/Module/Lockview.php @@ -0,0 +1,152 @@ + 1) ? argv(1) : 0); + if (is_numeric($type)) { + $item_id = intval($type); + $type='item'; + } + else { + $item_id = ((argc() > 2) ? intval(argv(2)) : 0); + } + + if(! $item_id) + killme(); + + if (!in_array($type, array('item','photo','event', 'menu_item', 'chatroom'))) + killme(); + + //we have different naming in in menu_item table and chatroom table + switch($type) { + case 'menu_item': + $id = 'mitem_id'; + break; + case 'chatroom': + $id = 'cr_id'; + break; + default: + $id = 'id'; + break; + } + + $r = q("SELECT * FROM %s WHERE $id = %d LIMIT 1", + dbesc($type), + intval($item_id) + ); + + if(! $r) + killme(); + + $item = $r[0]; + + //we have different naming in in menu_item table and chatroom table + switch($type) { + case 'menu_item': + $uid = $item['mitem_channel_id']; + break; + case 'chatroom': + $uid = $item['cr_uid']; + break; + default: + $uid = $item['uid']; + break; + } + + if($uid != local_channel()) { + echo '
  • ' . t('Remote privacy information not available.') . '
  • '; + killme(); + } + + if(($item['item_private'] == 1) && (! strlen($item['allow_cid'])) && (! strlen($item['allow_gid'])) + && (! strlen($item['deny_cid'])) && (! strlen($item['deny_gid']))) { + + // if the post is private, but public_policy is blank ("visible to the internet"), and there aren't any + // specific recipients, we're the recipient of a post with "bcc" or targeted recipients; so we'll just show it + // as unknown specific recipients. The sender will have the visibility list and will fall through to the + // next section. + + echo '
  • ' . translate_scope((! $item['public_policy']) ? 'specific' : $item['public_policy']) . '
  • '; + killme(); + } + + $allowed_users = expand_acl($item['allow_cid']); + $allowed_groups = expand_acl($item['allow_gid']); + $deny_users = expand_acl($item['deny_cid']); + $deny_groups = expand_acl($item['deny_gid']); + + $o = '
  • ' . t('Visible to:') . '
  • '; + $l = array(); + + stringify_array_elms($allowed_groups,true); + stringify_array_elms($allowed_users,true); + stringify_array_elms($deny_groups,true); + stringify_array_elms($deny_users,true); + + if(count($allowed_groups)) { + $r = q("SELECT gname FROM `groups` WHERE hash IN ( " . implode(', ', $allowed_groups) . " )"); + if($r) + foreach($r as $rr) + $l[] = '
  • ' . $rr['gname'] . '
  • '; + } + if(count($allowed_users)) { + $r = q("SELECT xchan_name FROM xchan WHERE xchan_hash IN ( " . implode(', ',$allowed_users) . " )"); + if($r) + foreach($r as $rr) + $l[] = '
  • ' . $rr['xchan_name'] . '
  • '; + if($atokens) { + foreach($atokens as $at) { + if(in_array("'" . $at['xchan_hash'] . "'",$allowed_users)) { + $l[] = '
  • ' . $at['xchan_name'] . '
  • '; + } + } + } + } + if(count($deny_groups)) { + $r = q("SELECT gname FROM `groups` WHERE hash IN ( " . implode(', ', $deny_groups) . " )"); + if($r) + foreach($r as $rr) + $l[] = '
  • ' . $rr['gname'] . '
  • '; + } + if(count($deny_users)) { + $r = q("SELECT xchan_name FROM xchan WHERE xchan_hash IN ( " . implode(', ', $deny_users) . " )"); + if($r) + foreach($r as $rr) + $l[] = '
  • ' . $rr['xchan_name'] . '
  • '; + + if($atokens) { + foreach($atokens as $at) { + if(in_array("'" . $at['xchan_hash'] . "'",$deny_users)) { + $l[] = '
  • ' . $at['xchan_name'] . '
  • '; + } + } + } + + + } + + echo $o . implode($l); + killme(); + + + } + +} diff --git a/Zotlabs/Module/Locs.php b/Zotlabs/Module/Locs.php new file mode 100644 index 000000000..4b1e3ffe2 --- /dev/null +++ b/Zotlabs/Module/Locs.php @@ -0,0 +1,132 @@ + t('Manage Channel Locations'), + '$loc' => t('Location'), + '$addr' => t('Address'), + '$mkprm' => t('Primary'), + '$drop' => t('Drop'), + '$submit' => t('Submit'), + '$sync' => t('Sync Now'), + '$sync_text' => t('Please wait several minutes between consecutive operations.'), + '$drop_text' => t('When possible, drop a location by logging into that website/hub and removing your channel.'), + '$last_resort' => t('Use this form to drop the location if the hub is no longer operating.'), + '$hubs' => $r + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Login.php b/Zotlabs/Module/Login.php new file mode 100644 index 000000000..ae35b922f --- /dev/null +++ b/Zotlabs/Module/Login.php @@ -0,0 +1,16 @@ + get_config('system','sitename'), + '$siteurl' => z_root(), + '$username' => sprintf( t('Site Member (%s)'), $email), + '$email' => $email, + '$reset_link' => z_root() . '/lostpass?verify=' . $hash + )); + + $subject = email_header_encode(sprintf( t('Password reset requested at %s'),get_config('system','sitename')), 'UTF-8'); + + $res = mail($email, $subject , + $message, + 'From: Administrator@' . $_SERVER['SERVER_NAME'] . "\n" + . 'Content-type: text/plain; charset=UTF-8' . "\n" + . 'Content-transfer-encoding: 8bit' ); + + + goaway(z_root()); + } + + + function get() { + + + if(x($_GET,'verify')) { + $verify = $_GET['verify']; + + $r = q("SELECT * FROM account WHERE account_reset = '%s' LIMIT 1", + dbesc($verify) + ); + if(! $r) { + notice( t("Request could not be verified. (You may have previously submitted it.) Password reset failed.") . EOL); + goaway(z_root()); + return; + } + + $aid = $r[0]['account_id']; + $email = $r[0]['account_email']; + + $new_password = autoname(6) . mt_rand(100,9999); + + $salt = random_string(32); + $password_encoded = hash('whirlpool', $salt . $new_password); + + $r = q("UPDATE account SET account_salt = '%s', account_password = '%s', account_reset = '', account_flags = (account_flags & ~%d) where account_id = %d", + dbesc($salt), + dbesc($password_encoded), + intval(ACCOUNT_UNVERIFIED), + intval($aid) + ); + + if($r) { + $tpl = get_markup_template('pwdreset.tpl'); + $o .= replace_macros($tpl,array( + '$lbl1' => t('Password Reset'), + '$lbl2' => t('Your password has been reset as requested.'), + '$lbl3' => t('Your new password is'), + '$lbl4' => t('Save or copy your new password - and then'), + '$lbl5' => '' . t('click here to login') . '.', + '$lbl6' => t('Your password may be changed from the Settings page after successful login.'), + '$newpass' => $new_password, + '$baseurl' => z_root() + + )); + + info("Your password has been reset." . EOL); + + $email_tpl = get_intltext_template("passchanged_eml.tpl"); + $message = replace_macros($email_tpl, array( + '$sitename' => \App::$config['sitename'], + '$siteurl' => z_root(), + '$username' => sprintf( t('Site Member (%s)'), $email), + '$email' => $email, + '$new_password' => $new_password, + '$uid' => $newuid )); + + $subject = email_header_encode( sprintf( t('Your password has changed at %s'), get_config('system','sitename')), 'UTF-8'); + + $res = mail($email,$subject,$message, + 'From: ' . 'Administrator@' . $_SERVER['SERVER_NAME'] . "\n" + . 'Content-type: text/plain; charset=UTF-8' . "\n" + . 'Content-transfer-encoding: 8bit' ); + + return $o; + } + + } + else { + $tpl = get_markup_template('lostpass.tpl'); + + $o .= replace_macros($tpl,array( + '$title' => t('Forgot your Password?'), + '$desc' => t('Enter your email address and submit to have your password reset. Then check your email for further instructions.'), + '$name' => t('Email Address'), + '$submit' => t('Reset') + )); + + return $o; + } + + } + +} diff --git a/Zotlabs/Module/Magic.php b/Zotlabs/Module/Magic.php new file mode 100644 index 000000000..6798f72a9 --- /dev/null +++ b/Zotlabs/Module/Magic.php @@ -0,0 +1,171 @@ + false, 'url' => '', 'message' => ''); + logger('mod_magic: invoked', LOGGER_DEBUG); + + logger('mod_magic: args: ' . print_r($_REQUEST,true),LOGGER_DATA); + + $addr = ((x($_REQUEST,'addr')) ? $_REQUEST['addr'] : ''); + $dest = ((x($_REQUEST,'dest')) ? $_REQUEST['dest'] : ''); + $test = ((x($_REQUEST,'test')) ? intval($_REQUEST['test']) : 0); + $rev = ((x($_REQUEST,'rev')) ? intval($_REQUEST['rev']) : 0); + $delegate = ((x($_REQUEST,'delegate')) ? $_REQUEST['delegate'] : ''); + + $parsed = parse_url($dest); + if(! $parsed) { + if($test) { + $ret['message'] .= 'could not parse ' . $dest . EOL; + return($ret); + } + goaway($dest); + } + + $basepath = $parsed['scheme'] . '://' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : ''); + + $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", + dbesc($basepath) + ); + + if(! $x) { + + /* + * We have no records for, or prior communications with this hub. + * If an address was supplied, let's finger them to create a hub record. + * Otherwise we'll use the special address '[system]' which will return + * either a system channel or the first available normal channel. We don't + * really care about what channel is returned - we need the hub information + * from that response so that we can create signed auth packets destined + * for that hub. + * + */ + + $j = \Zotlabs\Zot\Finger::run((($addr) ? $addr : '[system]@' . $parsed['host']),null); + if($j['success']) { + import_xchan($j); + + // Now try again + + $x = q("select * from hubloc where hubloc_url = '%s' order by hubloc_connected desc limit 1", + dbesc($basepath) + ); + } + } + + if(! $x) { + if($rev) + goaway($dest); + else { + logger('mod_magic: no channels found for requested hub.' . print_r($_REQUEST,true)); + if($test) { + $ret['message'] .= 'This site has no previous connections with ' . $basepath . EOL; + return $ret; + } + notice( t('Hub not found.') . EOL); + return; + } + } + + // This is ready-made for a plugin that provides a blacklist or "ask me" before blindly authenticating. + // By default, we'll proceed without asking. + + $arr = array( + 'channel_id' => local_channel(), + 'xchan' => $x[0], + 'destination' => $dest, + 'proceed' => true + ); + + call_hooks('magic_auth',$arr); + $dest = $arr['destination']; + if(! $arr['proceed']) { + if($test) { + $ret['message'] .= 'cancelled by plugin.' . EOL; + return $ret; + } + goaway($dest); + } + + if((get_observer_hash()) && ($x[0]['hubloc_url'] === z_root())) { + // We are already authenticated on this site and a registered observer. + // Just redirect. + if($test) { + $ret['success'] = true; + $ret['message'] .= 'Local site - you are already authenticated.' . EOL; + return $ret; + } + + $delegation_success = false; + if($delegate) { + $r = q("select * from channel left join hubloc on channel_hash = hubloc_hash where hubloc_addr = '%s' limit 1", + dbesc($delegate) + ); + + if($r && intval($r[0]['channel_id'])) { + $allowed = perm_is_allowed($r[0]['channel_id'],get_observer_hash(),'delegate'); + if($allowed) { + $_SESSION['delegate_channel'] = $r[0]['channel_id']; + $_SESSION['delegate'] = get_observer_hash(); + $_SESSION['account_id'] = intval($r[0]['channel_account_id']); + change_channel($r[0]['channel_id']); + + $delegation_success = true; + } + } + } + + + + // FIXME: check and honour local delegation + + + goaway($dest); + } + + if(local_channel()) { + $channel = \App::get_channel(); + + $token = random_string(); + $token_sig = base64url_encode(rsa_sign($token,$channel['channel_prvkey'])); + + $channel['token'] = $token; + $channel['token_sig'] = $token_sig; + + \Zotlabs\Zot\Verify::create('auth',$channel['channel_id'],$token,$x[0]['hubloc_url']); + + $target_url = $x[0]['hubloc_callback'] . '/?f=&auth=' . urlencode($channel['channel_address'] . '@' . \App::get_hostname()) + . '&sec=' . $token . '&dest=' . urlencode($dest) . '&version=' . ZOT_REVISION; + + if($delegate) + $target_url .= '&delegate=' . urlencode($delegate); + + logger('mod_magic: redirecting to: ' . $target_url, LOGGER_DEBUG); + + if($test) { + $ret['success'] = true; + $ret['url'] = $target_url; + $ret['message'] = 'token ' . $token . ' created for channel ' . $channel['channel_id'] . ' for url ' . $x[0]['hubloc_url'] . EOL; + return $ret; + } + + goaway($target_url); + + } + + if($test) { + $ret['message'] = 'Not authenticated or invalid arguments to mod_magic' . EOL; + return $ret; + } + + goaway($dest); + + } + +} diff --git a/Zotlabs/Module/Mail.php b/Zotlabs/Module/Mail.php new file mode 100644 index 000000000..043c28078 --- /dev/null +++ b/Zotlabs/Module/Mail.php @@ -0,0 +1,383 @@ + array($ret['conv']),'mail' => array(encode_mail($ret['mail'],true)))); + } + else { + notice($ret['message']); + } + + goaway(z_root() . '/mail/combined'); + + } + + function get() { + + $o = ''; + nav_set_selected('messages'); + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return login(); + } + + $channel = \App::get_channel(); + + head_set_icon($channel['xchan_photo_s']); + + $cipher = get_pconfig(local_channel(),'system','default_cipher'); + if(! $cipher) + $cipher = 'aes256'; + + $tpl = get_markup_template('mail_head.tpl'); + $header = replace_macros($tpl, array( + '$header' => t('Messages'), + )); + + if((argc() == 4) && (argv(2) === 'drop')) { + if(! intval(argv(3))) + return; + $cmd = argv(2); + $mailbox = argv(1); + $r = private_messages_drop(local_channel(), argv(3)); + if($r) { + //info( t('Message deleted.') . EOL ); + } + goaway(z_root() . '/mail/' . $mailbox); + } + + if((argc() == 4) && (argv(2) === 'recall')) { + if(! intval(argv(3))) + return; + $cmd = argv(2); + $mailbox = argv(1); + $r = q("update mail set mail_recalled = 1 where id = %d and channel_id = %d", + intval(argv(3)), + intval(local_channel()) + ); + $x = q("select * from mail where id = %d and channel_id = %d", + intval(argv(3)), + intval(local_channel()) + ); + if($x) { + build_sync_packet(local_channel(),array('mail' => encode_mail($x[0],true))); + } + + \Zotlabs\Daemon\Master::Summon(array('Notifier','mail',intval(argv(3)))); + + if($r) { + info( t('Message recalled.') . EOL ); + } + goaway(z_root() . '/mail/' . $mailbox . '/' . argv(3)); + + } + + if((argc() == 4) && (argv(2) === 'dropconv')) { + if(! intval(argv(3))) + return; + $cmd = argv(2); + $mailbox = argv(1); + $r = private_messages_drop(local_channel(), argv(3), true); + if($r) + info( t('Conversation removed.') . EOL ); + goaway(z_root() . '/mail/' . $mailbox); + } + + if((argc() > 1) && (argv(1) === 'new')) { + + $plaintext = true; + + $tpl = get_markup_template('msg-header.tpl'); + + $header = replace_macros($tpl, array( + '$baseurl' => z_root(), + '$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'), + '$nickname' => $channel['channel_address'], + '$linkurl' => t('Please enter a link URL:'), + '$expireswhen' => t('Expires YYYY-MM-DD HH:MM') + )); + + \App::$page['htmlhead'] .= $header; + + $prename = ''; + $preid = ''; + + if(x($_REQUEST,'hash')) { + + $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash + where abook_channel = %d and abook_xchan = '%s' limit 1", + intval(local_channel()), + dbesc($_REQUEST['hash']) + ); + + if(!$r) { + $r = q("select * from xchan where xchan_hash = '%s' and xchan_network = 'zot' limit 1", + dbesc($_REQUEST['hash']) + ); + } + + if($r) { + $prename = (($r[0]['abook_id']) ? $r[0]['xchan_name'] : $r[0]['xchan_addr']); + $preurl = $r[0]['xchan_url']; + $preid = (($r[0]['abook_id']) ? ($r[0]['xchan_hash']) : ''); + } + else { + notice( t('Requested channel is not in this network') . EOL ); + } + + } + + $tpl = get_markup_template('prv_message.tpl'); + $o .= replace_macros($tpl,array( + '$new' => true, + '$header' => t('Send Private Message'), + '$to' => t('To:'), + '$prefill' => $prename, + '$preid' => $preid, + '$subject' => t('Subject:'), + '$subjtxt' => ((x($_REQUEST,'subject')) ? strip_tags($_REQUEST['subject']) : ''), + '$text' => ((x($_REQUEST,'body')) ? htmlspecialchars($_REQUEST['body'], ENT_COMPAT, 'UTF-8') : ''), + '$yourmessage' => t('Your message:'), + '$parent' => '', + '$attach' => t('Attach file'), + '$insert' => t('Insert web link'), + '$submit' => t('Send'), + '$defexpire' => '', + '$feature_expire' => ((feature_enabled(local_channel(),'content_expire')) ? true : false), + '$expires' => t('Set expiration date'), + '$feature_encrypt' => ((feature_enabled(local_channel(),'content_encrypt')) ? true : false), + '$encrypt' => t('Encrypt text'), + '$cipher' => $cipher, + )); + + return $o; + } + + switch(argv(1)) { + case 'combined': + $mailbox = 'combined'; + break; + case 'inbox': + $mailbox = 'inbox'; + break; + case 'outbox': + $mailbox = 'outbox'; + break; + default: + $mailbox = 'combined'; + break; + } + + $last_message = private_messages_list(local_channel(), $mailbox, 0, 1); + + $mid = ((argc() > 2) && (intval(argv(2)))) ? argv(2) : $last_message[0]['id']; + + $plaintext = true; + + // if( local_channel() && feature_enabled(local_channel(),'richtext') ) + // $plaintext = false; + + + + if($mailbox == 'combined') { + $messages = private_messages_fetch_conversation(local_channel(), $mid, true); + } + else { + $messages = private_messages_fetch_message(local_channel(), $mid, true); + } + + if(! $messages) { + //info( t('Message not found.') . EOL); + return; + } + + if($messages[0]['to_xchan'] === $channel['channel_hash']) + \App::$poi = $messages[0]['from']; + else + \App::$poi = $messages[0]['to']; + + $tpl = get_markup_template('msg-header.tpl'); + + \App::$page['htmlhead'] .= replace_macros($tpl, array( + '$nickname' => $channel['channel_address'], + '$baseurl' => z_root(), + '$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'), + '$linkurl' => t('Please enter a link URL:'), + '$expireswhen' => t('Expires YYYY-MM-DD HH:MM') + )); + + $mails = array(); + + $seen = 0; + $unknown = false; + + foreach($messages as $message) { + + $s = theme_attachments($message); + + $mails[] = array( + 'mailbox' => $mailbox, + 'id' => $message['id'], + 'mid' => $message['mid'], + 'from_name' => $message['from']['xchan_name'], + 'from_url' => chanlink_hash($message['from_xchan']), + 'from_photo' => $message['from']['xchan_photo_s'], + 'to_name' => $message['to']['xchan_name'], + 'to_url' => chanlink_hash($message['to_xchan']), + 'to_photo' => $message['to']['xchan_photo_s'], + 'subject' => $message['title'], + 'body' => smilies(bbcode($message['body'])), + 'attachments' => $s, + 'delete' => t('Delete message'), + 'dreport' => t('Delivery report'), + 'recall' => t('Recall message'), + 'can_recall' => (($channel['channel_hash'] == $message['from_xchan']) ? true : false), + 'is_recalled' => (intval($message['mail_recalled']) ? t('Message has been recalled.') : ''), + 'date' => datetime_convert('UTC',date_default_timezone_get(),$message['created'], 'c'), + ); + + $seen = $message['seen']; + + } + + $recp = (($message['from_xchan'] === $channel['channel_hash']) ? 'to' : 'from'); + + $tpl = get_markup_template('mail_display.tpl'); + $o = replace_macros($tpl, array( + '$mailbox' => $mailbox, + '$prvmsg_header' => $message['title'], + '$thread_id' => $mid, + '$thread_subject' => $message['title'], + '$thread_seen' => $seen, + '$delete' => t('Delete Conversation'), + '$canreply' => (($unknown) ? false : '1'), + '$unknown_text' => t("No secure communications available. You may be able to respond from the sender's profile page."), + '$mails' => $mails, + + // reply + '$header' => t('Send Reply'), + '$to' => t('To:'), + '$reply' => true, + '$subject' => t('Subject:'), + '$subjtxt' => $message['title'], + '$yourmessage' => sprintf(t('Your message for %s (%s):'), $message[$recp]['xchan_name'], $message[$recp]['xchan_addr']), + '$text' => '', + '$parent' => $message['parent_mid'], + '$recphash' => $message[$recp]['xchan_hash'], + '$attach' => t('Attach file'), + '$insert' => t('Insert web link'), + '$submit' => t('Submit'), + '$defexpire' => '', + '$feature_expire' => ((feature_enabled(local_channel(),'content_expire')) ? true : false), + '$expires' => t('Set expiration date'), + '$feature_encrypt' => ((feature_enabled(local_channel(),'content_encrypt')) ? true : false), + '$encrypt' => t('Encrypt text'), + '$cipher' => $cipher, + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Manage.php b/Zotlabs/Module/Manage.php new file mode 100644 index 000000000..8f815d6d4 --- /dev/null +++ b/Zotlabs/Module/Manage.php @@ -0,0 +1,183 @@ + 1) ? intval(argv(1)) : 0); + + if((argc() > 2) && (argv(2) === 'default')) { + $r = q("select channel_id from channel where channel_id = %d and channel_account_id = %d limit 1", + intval($change_channel), + intval(get_account_id()) + ); + if($r) { + q("update account set account_default_channel = %d where account_id = %d", + intval($change_channel), + intval(get_account_id()) + ); + } + goaway(z_root() . '/manage'); + } + + + if($change_channel) { + + $r = change_channel($change_channel); + + if((argc() > 2) && !(argv(2) === 'default')) { + goaway(z_root() . '/' . implode('/',array_slice(\App::$argv,2))); // Go to whatever is after /manage/, but with the new channel + } + else { + if($r && $r['channel_startpage']) + goaway(z_root() . '/' . $r['channel_startpage']); // If nothing extra is specified, go to the default page + } + goaway(z_root()); + } + + $channels = null; + + if(local_channel()) { + $r = q("select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel.channel_account_id = %d and channel_removed = 0 order by channel_name ", + intval(get_account_id()) + ); + + $account = \App::get_account(); + + if($r && count($r)) { + $channels = $r; + for($x = 0; $x < count($channels); $x ++) { + $channels[$x]['link'] = 'manage/' . intval($channels[$x]['channel_id']); + $channels[$x]['default'] = (($channels[$x]['channel_id'] == $account['account_default_channel']) ? "1" : ''); + $channels[$x]['default_links'] = '1'; + + + $c = q("SELECT id, item_wall FROM item + WHERE item_unseen = 1 and uid = %d " . item_normal(), + intval($channels[$x]['channel_id']) + ); + + if($c) { + foreach ($c as $it) { + if(intval($it['item_wall'])) + $channels[$x]['home'] ++; + else + $channels[$x]['network'] ++; + } + } + + + $intr = q("SELECT COUNT(abook.abook_id) AS total FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ", + intval($channels[$x]['channel_id']) + ); + + if($intr) + $channels[$x]['intros'] = intval($intr[0]['total']); + + + $mails = q("SELECT count(id) as total from mail WHERE channel_id = %d AND mail_seen = 0 and from_xchan != '%s' ", + intval($channels[$x]['channel_id']), + dbesc($channels[$x]['channel_hash']) + ); + + if($mails) + $channels[$x]['mail'] = intval($mails[0]['total']); + + + $events = q("SELECT etype, dtstart, adjust FROM `event` + WHERE `event`.`uid` = %d AND dtstart < '%s' AND dtstart > '%s' and `dismissed` = 0 + ORDER BY `dtstart` ASC ", + intval($channels[$x]['channel_id']), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + 7 days')), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days')) + ); + + if($events) { + $channels[$x]['all_events'] = count($events); + + if($channels[$x]['all_events']) { + $str_now = datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d'); + foreach($events as $e) { + $bd = false; + if($e['etype'] === 'birthday') { + $channels[$x]['birthdays'] ++; + $bd = true; + } + else { + $channels[$x]['events'] ++; + } + if(datetime_convert('UTC', ((intval($e['adjust'])) ? date_default_timezone_get() : 'UTC'), $e['dtstart'], 'Y-m-d') === $str_now) { + $channels[$x]['all_events_today'] ++; + if($bd) + $channels[$x]['birthdays_today'] ++; + else + $channels[$x]['events_today'] ++; + } + } + } + } + } + } + + $r = q("select count(channel_id) as total from channel where channel_account_id = %d and channel_removed = 0", + intval(get_account_id()) + ); + $limit = account_service_class_fetch(get_account_id(),'total_identities'); + if($limit !== false) { + $channel_usage_message = sprintf( t("You have created %1$.0f of %2$.0f allowed channels."), $r[0]['total'], $limit); + } + else { + $channel_usage_message = ''; + } + } + + $create = array( 'new_channel', t('Create a new channel'), t('Create New')); + + $delegates = q("select * from abook left join xchan on abook_xchan = xchan_hash where + abook_channel = %d and abook_xchan in ( select xchan from abconfig where chan = %d and cat = 'their_perms' and k = 'delegate' and v = 1 )", + intval(local_channel()), + intval(local_channel()) + ); + + if($delegates) { + for($x = 0; $x < count($delegates); $x ++) { + $delegates[$x]['link'] = 'magic?f=&dest=' . urlencode($delegates[$x]['xchan_url']) + . '&delegate=' . urlencode($delegates[$x]['xchan_addr']); + $delegates[$x]['channel_name'] = $delegates[$x]['xchan_name']; + $delegates[$x]['delegate'] = 1; + } + } + else { + $delegates = null; + } + + $o = replace_macros(get_markup_template('channels.tpl'), array( + '$header' => t('Channel Manager'), + '$msg_selected' => t('Current Channel'), + '$selected' => local_channel(), + '$desc' => t('Switch to one of your channels by selecting it.'), + '$msg_default' => t('Default Channel'), + '$msg_make_default' => t('Make Default'), + '$create' => $create, + '$all_channels' => $channels, + '$mail_format' => t('%d new messages'), + '$intros_format' => t('%d new introductions'), + '$channel_usage_message' => $channel_usage_message, + '$delegated_desc' => t('Delegated Channel'), + '$delegates' => $delegates + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Match.php b/Zotlabs/Module/Match.php new file mode 100644 index 000000000..c422e4b3e --- /dev/null +++ b/Zotlabs/Module/Match.php @@ -0,0 +1,84 @@ +' . t('Profile Match') . ''; + + $r = q("SELECT `keywords` FROM `profile` WHERE `is_default` = 1 AND `uid` = %d LIMIT 1", + intval(local_channel()) + ); + if (! count($r)) + return; + + if (! $r[0]['keywords']) { + notice( t('No keywords to match. Please add keywords to your default profile.') . EOL); + return; + } + + $params = array(); + $tags = trim($r[0]['keywords']); + + if ($tags) { + $params['s'] = $tags; + if (\App::$pager['page'] != 1) + $params['p'] = \App::$pager['page']; + + // if(strlen(get_config('system','directory_submit_url'))) + // $x = post_url('http://dir.friendica.com/msearch', $params); + // else + // $x = post_url(z_root() . '/msearch', $params); + + $j = json_decode($x); + + if ($j->total) { + \App::set_pager_total($j->total); + \App::set_pager_itemspage($j->items_page); + } + + if (count($j->results)) { + $tpl = get_markup_template('match.tpl'); + foreach ($j->results as $jj) { + $connlnk = z_root() . '/follow/?url=' . $jj->url; + $o .= replace_macros($tpl,array( + '$url' => zid($jj->url), + '$name' => $jj->name, + '$photo' => $jj->photo, + '$inttxt' => ' ' . t('is interested in:'), + '$conntxt' => t('Connect'), + '$connlnk' => $connlnk, + '$tags' => $jj->tags + )); + } + } else { + info( t('No matches') . EOL); + } + } + + $o .= cleardiv(); + $o .= paginate($a); + + return $o; + } + +} diff --git a/Zotlabs/Module/Menu.php b/Zotlabs/Module/Menu.php new file mode 100644 index 000000000..e98053f8c --- /dev/null +++ b/Zotlabs/Module/Menu.php @@ -0,0 +1,173 @@ + 1) ? intval(argv(1)) : 0); + if($menu_id) { + $_REQUEST['menu_id'] = intval(argv(1)); + $r = menu_edit($_REQUEST); + if($r) { + menu_sync_packet($uid,get_observer_hash(),$menu_id); + //info( t('Menu updated.') . EOL); + goaway(z_root() . '/mitem/' . $menu_id . ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + else + notice( t('Unable to update menu.'). EOL); + } + else { + $r = menu_create($_REQUEST); + if($r) { + menu_sync_packet($uid,get_observer_hash(),$r); + + //info( t('Menu created.') . EOL); + goaway(z_root() . '/mitem/' . $r . ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + else + notice( t('Unable to create menu.'). EOL); + + } + } + + + + + function get() { + + $uid = local_channel(); + + if (\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + $uid = intval($sys['channel_id']); + } + + if(! $uid) { + notice( t('Permission denied.') . EOL); + return ''; + } + + if(argc() == 1) { + + + + // list menus + $x = menu_list($uid); + if($x) { + for($y = 0; $y < count($x); $y ++) { + $m = menu_fetch($x[$y]['menu_name'],$uid,get_observer_hash()); + if($m) + $x[$y]['element'] = '[element]' . base64url_encode(json_encode(menu_element($m))) . '[/element]'; + $x[$y]['bookmark'] = (($x[$y]['menu_flags'] & MENU_BOOKMARK) ? true : false); + } + } + + $create = replace_macros(get_markup_template('menuedit.tpl'), array( + '$menu_name' => array('menu_name', t('Menu Name'), '', t('Unique name (not visible on webpage) - required'), '*'), + '$menu_desc' => array('menu_desc', t('Menu Title'), '', t('Visible on webpage - leave empty for no title'), ''), + '$menu_bookmark' => array('menu_bookmark', t('Allow Bookmarks'), 0 , t('Menu may be used to store saved bookmarks'), array(t('No'), t('Yes'))), + '$submit' => t('Submit and proceed'), + '$sys' => \App::$is_sys, + '$display' => 'none' + )); + + $o = replace_macros(get_markup_template('menulist.tpl'),array( + '$title' => t('Menus'), + '$create' => $create, + '$menus' => $x, + '$nametitle' => t('Menu Name'), + '$desctitle' => t('Menu Title'), + '$edit' => t('Edit'), + '$drop' => t('Drop'), + '$created' => t('Created'), + '$edited' => t('Edited'), + '$new' => t('New'), + '$bmark' => t('Bookmarks allowed'), + '$hintnew' => t('Create'), + '$hintdrop' => t('Delete this menu'), + '$hintcontent' => t('Edit menu contents'), + '$hintedit' => t('Edit this menu'), + '$sys' => \App::$is_sys + )); + + return $o; + + } + + if(argc() > 1) { + if(intval(argv(1))) { + + if(argc() == 3 && argv(2) == 'drop') { + menu_sync_packet($uid,get_observer_hash(),intval(argv(1)),true); + $r = menu_delete_id(intval(argv(1)),$uid); + if(!$r) + notice( t('Menu could not be deleted.'). EOL); + + goaway(z_root() . '/menu' . ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + + $m = menu_fetch_id(intval(argv(1)),$uid); + + if(! $m) { + notice( t('Menu not found.') . EOL); + return ''; + } + + $o = replace_macros(get_markup_template('menuedit.tpl'), array( + '$header' => t('Edit Menu'), + '$sys' => \App::$is_sys, + '$menu_id' => intval(argv(1)), + '$menu_edit_link' => 'mitem/' . intval(argv(1)) . ((\App::$is_sys) ? '?f=&sys=1' : ''), + '$hintedit' => t('Add or remove entries to this menu'), + '$editcontents' => t('Edit menu contents'), + '$menu_name' => array('menu_name', t('Menu name'), $m['menu_name'], t('Must be unique, only seen by you'), '*'), + '$menu_desc' => array('menu_desc', t('Menu title'), $m['menu_desc'], t('Menu title as seen by others'), ''), + '$menu_bookmark' => array('menu_bookmark', t('Allow bookmarks'), (($m['menu_flags'] & MENU_BOOKMARK) ? 1 : 0), t('Menu may be used to store saved bookmarks'), array(t('No'), t('Yes'))), + '$menu_system' => (($m['menu_flags'] & MENU_SYSTEM) ? 1 : 0), + '$submit' => t('Submit and proceed') + )); + + return $o; + + } + else { + notice( t('Not found.') . EOL); + return; + } + } + + } + +} diff --git a/Zotlabs/Module/Message.php b/Zotlabs/Module/Message.php new file mode 100644 index 000000000..ea2127a1d --- /dev/null +++ b/Zotlabs/Module/Message.php @@ -0,0 +1,108 @@ + $rr['id'], + 'from_name' => $rr['from']['xchan_name'], + 'from_url' => chanlink_hash($rr['from_xchan']), + 'from_photo' => $rr['from']['xchan_photo_s'], + 'to_name' => $rr['to']['xchan_name'], + 'to_url' => chanlink_hash($rr['to_xchan']), + 'to_photo' => $rr['to']['xchan_photo_s'], + 'subject' => (($rr['seen']) ? $rr['title'] : '' . $rr['title'] . ''), + 'delete' => t('Delete conversation'), + 'body' => smilies(bbcode($rr['body'])), + 'date' => datetime_convert('UTC',date_default_timezone_get(),$rr['created'], t('D, d M Y - g:i A')), + 'seen' => $rr['seen'] + ); + } + + + $tpl = get_markup_template('mail_head.tpl'); + $o = replace_macros($tpl, array( + '$header' => $header, + '$messages' => $messages + )); + + + $o .= alt_pager($a,count($r)); + + return $o; + + return; + + } + */ + + return; + } + +} diff --git a/Zotlabs/Module/Mitem.php b/Zotlabs/Module/Mitem.php new file mode 100644 index 000000000..b64b50c8e --- /dev/null +++ b/Zotlabs/Module/Mitem.php @@ -0,0 +1,245 @@ + 2) ? intval(argv(2)) : 0); + if($mitem_id) { + $_REQUEST['mitem_id'] = $mitem_id; + $r = menu_edit_item($_REQUEST['menu_id'],$uid,$_REQUEST); + if($r) { + menu_sync_packet($uid,get_observer_hash(),$_REQUEST['menu_id']); + //info( t('Menu element updated.') . EOL); + goaway(z_root() . '/mitem/' . $_REQUEST['menu_id'] . ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + else + notice( t('Unable to update menu element.') . EOL); + + } + else { + $r = menu_add_item($_REQUEST['menu_id'],$uid,$_REQUEST); + if($r) { + menu_sync_packet($uid,get_observer_hash(),$_REQUEST['menu_id']); + //info( t('Menu element added.') . EOL); + if($_REQUEST['submit']) { + goaway(z_root() . '/menu' . ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + if($_REQUEST['submit-more']) { + goaway(z_root() . '/mitem/' . $_REQUEST['menu_id'] . '?f=&display=block' . ((\App::$is_sys) ? '&sys=1' : '') ); + } + } + else + notice( t('Unable to add menu element.') . EOL); + + } + + } + + + function get() { + + $uid = local_channel(); + $channel = \App::get_channel(); + $observer = \App::get_observer(); + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + $uid = intval($sys['channel_id']); + $channel = $sys; + $ob_hash = $sys['xchan_hash']; + } + + if(! $uid) { + notice( t('Permission denied.') . EOL); + return ''; + } + + if(argc() < 2 || (! \App::$data['menu'])) { + notice( t('Not found.') . EOL); + return ''; + } + + $m = menu_fetch(\App::$data['menu']['menu_name'],$uid,$ob_hash); + \App::$data['menu_item'] = $m; + + $menu_list = menu_list($uid); + + foreach($menu_list as $menus) { + if($menus['menu_name'] != $m['menu']['menu_name']) + $menu_names[] = $menus['menu_name']; + } + + $acl = new \Zotlabs\Access\AccessList($channel); + + $lockstate = (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'); + + if(argc() == 2) { + $r = q("select * from menu_item where mitem_menu_id = %d and mitem_channel_id = %d order by mitem_order asc, mitem_desc asc", + intval(\App::$data['menu']['menu_id']), + intval($uid) + ); + + if($_GET['display']) { + $display = $_GET['display']; + } + else { + $display = (($r) ? 'none' : 'block'); + } + + $create = replace_macros(get_markup_template('mitemedit.tpl'), array( + '$menu_id' => \App::$data['menu']['menu_id'], + '$permissions' => t('Menu Item Permissions'), + '$permdesc' => t("\x28click to open/close\x29"), + '$aclselect' => populate_acl($acl->get(),false), + '$mitem_desc' => array('mitem_desc', t('Link Name'), '', 'Visible name of the link','*'), + '$mitem_link' => array('mitem_link', t('Link or Submenu Target'), '', t('Enter URL of the link or select a menu name to create a submenu'), '*', 'list="menu-names"'), + '$usezid' => array('usezid', t('Use magic-auth if available'), true, '', array(t('No'), t('Yes'))), + '$newwin' => array('newwin', t('Open link in new window'), false,'', array(t('No'), t('Yes'))), + '$mitem_order' => array('mitem_order', t('Order in list'),'0',t('Higher numbers will sink to bottom of listing')), + '$submit' => t('Submit and finish'), + '$submit_more' => t('Submit and continue'), + '$display' => $display, + '$lockstate' => $lockstate, + '$menu_names' => $menu_names, + '$sys' => \App::$is_sys + )); + + $o .= replace_macros(get_markup_template('mitemlist.tpl'),array( + '$title' => t('Menu:'), + '$create' => $create, + '$nametitle' => t('Link Name'), + '$targettitle' => t('Link Target'), + '$menuname' => \App::$data['menu']['menu_name'], + '$menudesc' => \App::$data['menu']['menu_desc'], + '$edmenu' => t('Edit menu'), + '$menu_id' => \App::$data['menu']['menu_id'], + '$mlist' => $r, + '$edit' => t('Edit element'), + '$drop' => t('Drop element'), + '$new' => t('New element'), + '$hintmenu' => t('Edit this menu container'), + '$hintnew' => t('Add menu element'), + '$hintdrop' => t('Delete this menu item'), + '$hintedit' => t('Edit this menu item'), + )); + + return $o; + } + + + if(argc() > 2) { + + if(intval(argv(2))) { + + $m = q("select * from menu_item where mitem_id = %d and mitem_channel_id = %d limit 1", + intval(argv(2)), + intval($uid) + ); + + if(! $m) { + notice( t('Menu item not found.') . EOL); + goaway(z_root() . '/menu'. ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + + $mitem = $m[0]; + + $lockstate = (($mitem['allow_cid'] || $mitem['allow_gid'] || $mitem['deny_cid'] || $mitem['deny_gid']) ? 'lock' : 'unlock'); + + if(argc() == 4 && argv(3) == 'drop') { + menu_sync_packet($uid,get_observer_hash(),$mitem['mitem_menu_id']); + $r = menu_del_item($mitem['mitem_menu_id'], $uid, intval(argv(2))); + menu_sync_packet($uid,get_observer_hash(),$mitem['mitem_menu_id']); + if($r) + info( t('Menu item deleted.') . EOL); + else + notice( t('Menu item could not be deleted.'). EOL); + + goaway(z_root() . '/mitem/' . $mitem['mitem_menu_id'] . ((\App::$is_sys) ? '?f=&sys=1' : '')); + } + + // edit menu item + $o = replace_macros(get_markup_template('mitemedit.tpl'), array( + '$header' => t('Edit Menu Element'), + '$menu_id' => \App::$data['menu']['menu_id'], + '$permissions' => t('Menu Item Permissions'), + '$permdesc' => t("\x28click to open/close\x29"), + '$aclselect' => populate_acl($mitem,false), + '$mitem_id' => intval(argv(2)), + '$mitem_desc' => array('mitem_desc', t('Link text'), $mitem['mitem_desc'], '','*'), + '$mitem_link' => array('mitem_link', t('Link or Submenu Target'), $mitem['mitem_link'], 'Enter URL of the link or select a menu name to create a submenu', '*', 'list="menu-names"'), + '$usezid' => array('usezid', t('Use magic-auth if available'), (($mitem['mitem_flags'] & MENU_ITEM_ZID) ? 1 : 0), '', array(t('No'), t('Yes'))), + '$newwin' => array('newwin', t('Open link in new window'), (($mitem['mitem_flags'] & MENU_ITEM_NEWWIN) ? 1 : 0),'', array(t('No'), t('Yes'))), + '$mitem_order' => array('mitem_order', t('Order in list'),$mitem['mitem_order'],t('Higher numbers will sink to bottom of listing')), + '$submit' => t('Submit'), + '$lockstate' => $lockstate, + '$menu_names' => $menu_names + )); + + return $o; + } + } + } + +} diff --git a/Zotlabs/Module/Mood.php b/Zotlabs/Module/Mood.php new file mode 100644 index 000000000..d1bd44526 --- /dev/null +++ b/Zotlabs/Module/Mood.php @@ -0,0 +1,146 @@ + $v) + if($v !== 'NOTRANSLATION') + $shortlist[] = array($k,$v); + + + $tpl = get_markup_template('mood_content.tpl'); + + $o = replace_macros($tpl,array( + '$title' => t('Mood'), + '$desc' => t('Set your current mood and tell your friends'), + '$verbs' => $shortlist, + '$parent' => $parent, + '$submit' => t('Submit'), + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Msearch.php b/Zotlabs/Module/Msearch.php new file mode 100644 index 000000000..e96f78e16 --- /dev/null +++ b/Zotlabs/Module/Msearch.php @@ -0,0 +1,47 @@ + $rr['name'], + 'url' => z_root() . '/channel/' . $rr['nickname'], + 'photo' => z_root() . '/photo/avatar/' . $rr['uid'], + 'tags' => str_replace(array(',',' '),array(' ',' '),$rr['keywords']) + ); + } + + $output = array('total' => $total, 'items_page' => $perpage, 'page' => $page + 1, 'results' => $results); + + echo json_encode($output); + + killme(); + + } +} diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php new file mode 100644 index 000000000..3b88cd8d6 --- /dev/null +++ b/Zotlabs/Module/Network.php @@ -0,0 +1,530 @@ + \App::$query_string); + + call_hooks('network_content_init', $arr); + + $channel = \App::get_channel(); + $item_normal = item_normal(); + + $datequery = $datequery2 = ''; + + $group = 0; + + $nouveau = false; + + $datequery = ((x($_GET,'dend') && is_a_date_arg($_GET['dend'])) ? notags($_GET['dend']) : ''); + $datequery2 = ((x($_GET,'dbegin') && is_a_date_arg($_GET['dbegin'])) ? notags($_GET['dbegin']) : ''); + $nouveau = ((x($_GET,'new')) ? intval($_GET['new']) : 0); + $gid = ((x($_GET,'gid')) ? intval($_GET['gid']) : 0); + $category = ((x($_REQUEST,'cat')) ? $_REQUEST['cat'] : ''); + $hashtags = ((x($_REQUEST,'tag')) ? $_REQUEST['tag'] : ''); + $verb = ((x($_REQUEST,'verb')) ? $_REQUEST['verb'] : ''); + + $search = (($_GET['search']) ? $_GET['search'] : ''); + if($search) { + if(strpos($search,'@') === 0) { + $r = q("select abook_id from abook left join xchan on abook_xchan = xchan_hash where xchan_name = '%s' and abook_channel = %d limit 1", + dbesc(substr($search,1)), + intval(local_channel()) + ); + if($r) { + $_GET['cid'] = $r[0]['abook_id']; + $search = $_GET['search'] = ''; + } + } + elseif(strpos($search,'#') === 0) { + $hashtags = substr($search,1); + $search = $_GET['search'] = ''; + } + } + + if($datequery) + $_GET['order'] = 'post'; + + + // filter by collection (e.g. group) + + if($gid) { + $r = q("SELECT * FROM groups WHERE id = %d AND uid = %d LIMIT 1", + intval($gid), + intval(local_channel()) + ); + if(! $r) { + if($update) + killme(); + notice( t('No such group') . EOL ); + goaway(z_root() . '/network'); + // NOTREACHED + } + + $group = $gid; + $group_hash = $r[0]['hash']; + $def_acl = array('allow_gid' => '<' . $r[0]['hash'] . '>'); + } + + $o = ''; + + + // if no tabs are selected, defaults to comments + + $cid = ((x($_GET,'cid')) ? intval($_GET['cid']) : 0); + $star = ((x($_GET,'star')) ? intval($_GET['star']) : 0); + $order = ((x($_GET,'order')) ? notags($_GET['order']) : 'comment'); + $liked = ((x($_GET,'liked')) ? intval($_GET['liked']) : 0); + $conv = ((x($_GET,'conv')) ? intval($_GET['conv']) : 0); + $spam = ((x($_GET,'spam')) ? intval($_GET['spam']) : 0); + $cmin = ((x($_GET,'cmin')) ? intval($_GET['cmin']) : 0); + $cmax = ((x($_GET,'cmax')) ? intval($_GET['cmax']) : 99); + $firehose = ((x($_GET,'fh')) ? intval($_GET['fh']) : 0); + $file = ((x($_GET,'file')) ? $_GET['file'] : ''); + + + $deftag = ''; + + if(x($_GET,'search') || x($_GET,'file')) + $nouveau = true; + if($cid) { + $r = q("SELECT abook_xchan FROM abook WHERE abook_id = %d AND abook_channel = %d LIMIT 1", + intval($cid), + intval(local_channel()) + ); + if(! $r) { + if($update) { + killme(); + } + notice( t('No such channel') . EOL ); + goaway(z_root() . '/network'); + // NOTREACHED + } + if($_GET['pf'] === '1') + $deftag = '@' . t('forum') . '+' . intval($cid) . '+'; + else + $def_acl = array('allow_cid' => '<' . $r[0]['abook_xchan'] . '>'); + } + + if(! $update) { + $tabs = network_tabs(); + $o .= $tabs; + + // search terms header + if($search) { + $o .= replace_macros(get_markup_template("section_title.tpl"),array( + '$title' => t('Search Results For:') . ' ' . htmlspecialchars($search, ENT_COMPAT,'UTF-8') + )); + } + + nav_set_selected('network'); + + $channel_acl = array( + 'allow_cid' => $channel['channel_allow_cid'], + 'allow_gid' => $channel['channel_allow_gid'], + 'deny_cid' => $channel['channel_deny_cid'], + 'deny_gid' => $channel['channel_deny_gid'] + ); + + $private_editing = ((($group || $cid) && (! intval($_GET['pf']))) ? true : false); + + $x = array( + 'is_owner' => true, + 'allow_location' => ((intval(get_pconfig($channel['channel_id'],'system','use_browser_location'))) ? '1' : ''), + 'default_location' => $channel['channel_location'], + 'nickname' => $channel['channel_address'], + 'lockstate' => (($private_editing || $channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), + 'acl' => populate_acl((($private_editing) ? $def_acl : $channel_acl), true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'), + 'bang' => (($private_editing) ? '!' : ''), + 'visitor' => true, + 'profile_uid' => local_channel(), + 'editor_autocomplete' => true, + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ); + if($deftag) + $x['pretext'] = $deftag; + + + $status_editor = status_editor($a,$x); + $o .= $status_editor; + + } + + + // We don't have to deal with ACL's on this page. You're looking at everything + // that belongs to you, hence you can see all of it. We will filter by group if + // desired. + + + $sql_options = (($star) + ? " and item_starred = 1 " + : ''); + + $sql_nets = ''; + + $sql_extra = " AND `item`.`parent` IN ( SELECT `parent` FROM `item` WHERE item_thread_top = 1 $sql_options ) "; + + if($group) { + $contact_str = ''; + $contacts = group_get_members($group); + if($contacts) { + foreach($contacts as $c) { + if($contact_str) + $contact_str .= ','; + $contact_str .= "'" . $c['xchan'] . "'"; + } + } + else { + $contact_str = ' 0 '; + info( t('Privacy group is empty')); + } + + $sql_extra = " AND item.parent IN ( SELECT DISTINCT parent FROM item WHERE true $sql_options AND (( author_xchan IN ( $contact_str ) OR owner_xchan in ( $contact_str )) or allow_gid like '" . protect_sprintf('%<' . dbesc($group_hash) . '>%') . "' ) and id = parent $item_normal ) "; + + $x = group_rec_byhash(local_channel(), $group_hash); + + if($x) { + $title = replace_macros(get_markup_template("section_title.tpl"),array( + '$title' => t('Privacy group: ') . $x['gname'] + )); + } + + $o = $tabs; + $o .= $title; + $o .= $status_editor; + + } + + elseif($cid) { + + $r = q("SELECT abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d and abook_channel = %d and abook_blocked = 0 limit 1", + intval($cid), + intval(local_channel()) + ); + if($r) { + $sql_extra = " AND item.parent IN ( SELECT DISTINCT parent FROM item WHERE true $sql_options AND uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($r[0]['abook_xchan']) . "' or owner_xchan = '" . dbesc($r[0]['abook_xchan']) . "' ) $item_normal ) "; + $title = replace_macros(get_markup_template("section_title.tpl"),array( + '$title' => '' . urlencode($r[0]['xchan_name']) . ' ' . $r[0]['xchan_name'] . '' + )); + $o = $tabs; + $o .= $title; + $o .= $status_editor; + } + else { + notice( t('Invalid connection.') . EOL); + goaway(z_root() . '/network'); + } + } + + if(x($category)) { + $sql_extra .= protect_sprintf(term_query('item', $category, TERM_CATEGORY)); + } + if(x($hashtags)) { + $sql_extra .= protect_sprintf(term_query('item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG)); + } + + if(! $update) { + // The special div is needed for liveUpdate to kick in for this page. + // We only launch liveUpdate if you aren't filtering in some incompatible + // way and also you aren't writing a comment (discovered in javascript). + + if($gid || $cid || $cmin || ($cmax != 99) || $star || $liked || $conv || $spam || $nouveau || $list) + $firehose = 0; + + $maxheight = get_pconfig(local_channel(),'system','network_divmore_height'); + if(! $maxheight) + $maxheight = 400; + + + $o .= '
    ' . "\r\n"; + $o .= "\r\n"; + + \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + '$baseurl' => z_root(), + '$pgtype' => 'network', + '$uid' => ((local_channel()) ? local_channel() : '0'), + '$gid' => (($gid) ? $gid : '0'), + '$cid' => (($cid) ? $cid : '0'), + '$cmin' => (($cmin) ? $cmin : '0'), + '$cmax' => (($cmax) ? $cmax : '0'), + '$star' => (($star) ? $star : '0'), + '$liked' => (($liked) ? $liked : '0'), + '$conv' => (($conv) ? $conv : '0'), + '$spam' => (($spam) ? $spam : '0'), + '$fh' => (($firehose) ? $firehose : '0'), + '$nouveau' => (($nouveau) ? $nouveau : '0'), + '$wall' => '0', + '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0), + '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$search' => (($search) ? $search : ''), + '$order' => $order, + '$file' => $file, + '$cats' => $category, + '$tags' => $hashtags, + '$dend' => $datequery, + '$mid' => '', + '$verb' => $verb, + '$dbegin' => $datequery2 + )); + } + + $sql_extra3 = ''; + + if($datequery) { + $sql_extra3 .= protect_sprintf(sprintf(" AND item.created <= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$datequery)))); + } + if($datequery2) { + $sql_extra3 .= protect_sprintf(sprintf(" AND item.created >= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$datequery2)))); + } + + $sql_extra2 = (($nouveau) ? '' : " AND item.parent = item.id "); + $sql_extra3 = (($nouveau) ? '' : $sql_extra3); + + if(x($_GET,'search')) { + $search = escape_tags($_GET['search']); + if(strpos($search,'#') === 0) { + $sql_extra .= term_query('item',substr($search,1),TERM_HASHTAG,TERM_COMMUNITYTAG); + } + else { + $sql_extra .= sprintf(" AND item.body like '%s' ", + dbesc(protect_sprintf('%' . $search . '%')) + ); + } + } + + if($verb) { + $sql_extra .= sprintf(" AND item.verb like '%s' ", + dbesc(protect_sprintf('%' . $verb . '%')) + ); + } + + if(strlen($file)) { + $sql_extra .= term_query('item',$file,TERM_FILE); + } + + if($conv) { + $sql_extra .= sprintf(" AND parent IN (SELECT distinct(parent) from item where ( author_xchan like '%s' or item_mentionsme = 1 )) ", + dbesc(protect_sprintf($channel['channel_hash'])) + ); + } + + if($update && ! $load) { + + // only setup pagination on initial page view + $pager_sql = ''; + + } + else { + $itemspage = get_pconfig(local_channel(),'system','itemspage'); + \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + } + + + if(($cmin != 0) || ($cmax != 99)) { + + // Not everybody who shows up in the network stream will be in your address book. + // By default those that aren't are assumed to have closeness = 99; but this isn't + // recorded anywhere. So if cmax is 99, we'll open the search up to anybody in + // the stream with a NULL address book entry. + + $sql_nets .= " AND "; + + if($cmax == 99) + $sql_nets .= " ( "; + + $sql_nets .= "( abook.abook_closeness >= " . intval($cmin) . " "; + $sql_nets .= " AND abook.abook_closeness <= " . intval($cmax) . " ) "; + + if($cmax == 99) + $sql_nets .= " OR abook.abook_closeness IS NULL ) "; + + + } + + $abook_uids = " and abook.abook_channel = " . local_channel() . " "; + + if($firehose && (! get_config('system','disable_discover_tab'))) { + require_once('include/channel.php'); + $sys = get_sys_channel(); + $uids = " and item.uid = " . intval($sys['channel_id']) . " "; + \App::$data['firehose'] = intval($sys['channel_id']); + } + else { + $uids = " and item.uid = " . local_channel() . " "; + } + + if(get_pconfig(local_channel(),'system','network_list_mode')) + $page_mode = 'list'; + else + $page_mode = 'client'; + + $simple_update = (($update) ? " and item_unseen = 1 " : ''); + + // This fixes a very subtle bug so I'd better explain it. You wake up in the morning or return after a day + // or three and look at your matrix page - after opening up your browser. The first page loads just as it + // should. All of a sudden a few seconds later, page 2 will get inserted at the beginning of the page + // (before the page 1 content). The update code is actually doing just what it's supposed + // to, it's fetching posts that have the ITEM_UNSEEN bit set. But the reason that page 2 content is being + // returned in an UPDATE is because you hadn't gotten that far yet - you're still on page 1 and everything + // that we loaded for page 1 is now marked as seen. But the stuff on page 2 hasn't been. So... it's being + // treated as "new fresh" content because it is unseen. We need to distinguish it somehow from content + // which "arrived as you were reading page 1". We're going to do this + // by storing in your session the current UTC time whenever you LOAD a network page, and only UPDATE items + // which are both ITEM_UNSEEN and have "changed" since that time. Cross fingers... + + if($update && $_SESSION['loadtime']) + $simple_update = " AND (( item_unseen = 1 AND item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) OR item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) "; + if($load) + $simple_update = ''; + + if($nouveau && $load) { + // "New Item View" - show all items unthreaded in reverse created date order + + $items = q("SELECT item.*, item.id AS item_id, received FROM item + left join abook on ( item.owner_xchan = abook.abook_xchan $abook_uids ) + WHERE true $uids $item_normal + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $simple_update + $sql_extra $sql_nets + ORDER BY item.received DESC $pager_sql " + ); + + require_once('include/items.php'); + + xchan_query($items); + + $items = fetch_post_tags($items,true); + } + elseif($update) { + + // Normal conversation view + + if($order === 'post') + $ordering = "created"; + else + $ordering = "commented"; + + if($load) { + + // Fetch a page full of parent items for this page + + $r = q("SELECT distinct item.id AS item_id, $ordering FROM item + left join abook on ( item.owner_xchan = abook.abook_xchan $abook_uids ) + WHERE true $uids $item_normal + AND item.parent = item.id + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets + ORDER BY $ordering DESC $pager_sql " + ); + + } + else { + // this is an update + $r = q("SELECT item.parent AS item_id FROM item + left join abook on ( item.owner_xchan = abook.abook_xchan $abook_uids ) + WHERE true $uids $item_normal $simple_update + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets " + ); + $_SESSION['loadtime'] = datetime_convert(); + } + + // Then fetch all the children of the parents that are on this page + $parents_str = ''; + $update_unseen = ''; + + if($r) { + + $parents_str = ids_to_querystr($r,'item_id'); + + $items = q("SELECT item.*, item.id AS item_id FROM item + WHERE true $uids $item_normal + AND item.parent IN ( %s ) + $sql_extra ", + dbesc($parents_str) + ); + + xchan_query($items,true,(($firehose) ? local_channel() : 0)); + $items = fetch_post_tags($items,true); + $items = conv_sort($items,$ordering); + } + else { + $items = array(); + } + + if($page_mode === 'list') { + + /** + * in "list mode", only mark the parent item and any like activities as "seen". + * We won't distinguish between comment likes and post likes. The important thing + * is that the number of unseen comments will be accurate. The SQL to separate the + * comment likes could also get somewhat hairy. + */ + + if($parents_str) { + $update_unseen = " AND ( id IN ( " . dbesc($parents_str) . " )"; + $update_unseen .= " OR ( parent IN ( " . dbesc($parents_str) . " ) AND verb in ( '" . dbesc(ACTIVITY_LIKE) . "','" . dbesc(ACTIVITY_DISLIKE) . "' ))) "; + } + } + else { + if($parents_str) { + $update_unseen = " AND parent IN ( " . dbesc($parents_str) . " )"; + } + } + } + + if(($update_unseen) && (! $firehose)) + $r = q("UPDATE item SET item_unseen = 0 WHERE item_unseen = 1 AND uid = %d $update_unseen ", + intval(local_channel()) + ); + + $mode = (($nouveau) ? 'network-new' : 'network'); + + $o .= conversation($a,$items,$mode,$update,$page_mode); + + if(($items) && (! $update)) + $o .= alt_pager($a,count($items)); + + return $o; + } + +} diff --git a/Zotlabs/Module/New_channel.php b/Zotlabs/Module/New_channel.php new file mode 100644 index 000000000..26883b6e2 --- /dev/null +++ b/Zotlabs/Module/New_channel.php @@ -0,0 +1,151 @@ + 1) ? argv(1) : ''); + + if($cmd === 'autofill.json') { + require_once('library/urlify/URLify.php'); + $result = array('error' => false, 'message' => ''); + $n = trim($_REQUEST['name']); + + $x = strtolower(\URLify::transliterate($n)); + + $test = array(); + + // first name + if(strpos($x,' ')) + $test[] = legal_webbie(substr($x,0,strpos($x,' '))); + if($test[0]) { + // first name plus first initial of last + $test[] = ((strpos($x,' ')) ? $test[0] . legal_webbie(trim(substr($x,strpos($x,' '),2))) : ''); + // first name plus random number + $test[] = $test[0] . mt_rand(1000,9999); + } + // fullname + $test[] = legal_webbie($x); + // fullname plus random number + $test[] = legal_webbie($x) . mt_rand(1000,9999); + + json_return_and_die(check_webbie($test)); + } + + if($cmd === 'checkaddr.json') { + require_once('library/urlify/URLify.php'); + $result = array('error' => false, 'message' => ''); + $n = trim($_REQUEST['nick']); + + $x = strtolower(\URLify::transliterate($n)); + + $test = array(); + + $n = legal_webbie($x); + if(strlen($n)) { + $test[] = $n; + $test[] = $n . mt_rand(1000,9999); + } + + for($y = 0; $y < 100; $y ++) + $test[] = 'id' . mt_rand(1000,9999); + + json_return_and_die(check_webbie($test)); + } + + + } + + function post() { + + $arr = $_POST; + + $acc = \App::get_account(); + $arr['account_id'] = get_account_id(); + + // prevent execution by delegated channels as well as those not logged in. + // get_account_id() returns the account_id from the session. But \App::$account + // may point to the original authenticated account. + + if((! $acc) || ($acc['account_id'] != $arr['account_id'])) { + notice( t('Permission denied.') . EOL ); + return; + } + + $result = create_identity($arr); + + if(! $result['success']) { + notice($result['message']); + return; + } + + $newuid = $result['channel']['channel_id']; + + change_channel($result['channel']['channel_id']); + + if(! strlen($next_page = get_config('system','workflow_channel_next'))) + $next_page = 'settings'; + + goaway(z_root() . '/' . $next_page); + + } + + function get() { + + $acc = \App::get_account(); + + if((! $acc) || $acc['account_id'] != get_account_id()) { + notice( t('Permission denied.') . EOL); + return; + } + + $default_role = ''; + $aid = get_account_id(); + if($aid) { + $r = q("select count(channel_id) as total from channel where channel_account_id = %d", + intval($aid) + ); + if($r && (! intval($r[0]['total']))) { + $default_role = get_config('system','default_permissions_role'); + } + + $limit = account_service_class_fetch(get_account_id(),'total_identities'); + + if($r && ($limit !== false)) { + $channel_usage_message = sprintf( t("You have created %1$.0f of %2$.0f allowed channels."), $r[0]['total'], $limit); + } + else { + $channel_usage_message = ''; + } + } + + $name = array('name', t('Name or caption'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), t('Examples: "Bob Jameson", "Lisa and her Horses", "Soccer", "Aviation Group"'), "*"); + $nickhub = '@' . \App::get_hostname(); + $nickname = array('nickname', t('Choose a short nickname'), ((x($_REQUEST,'nickname')) ? $_REQUEST['nickname'] : ''), sprintf( t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), $nickhub), "*"); + $privacy_role = ((x($_REQUEST,'permissions_role')) ? $_REQUEST['permissions_role'] : "" ); + $role = array('permissions_role' , t('Channel role and privacy'), ($privacy_role) ? $privacy_role : 'social', t('Select a channel role with your privacy requirements.') . ' ' . t('Read more about roles') . '',get_roles()); + + $o = replace_macros(get_markup_template('new_channel.tpl'), array( + '$title' => t('Create Channel'), + '$desc' => t('A channel is your identity on this network. It can represent a person, a blog, or a forum to name a few. Channels can make connections with other channels to share information with highly detailed permissions.'), + '$label_import' => t('or import an existing channel from another location.'), + '$name' => $name, + '$role' => $role, + '$default_role' => $default_role, + '$nickname' => $nickname, + '$submit' => t('Create'), + '$channel_usage_message' => $channel_usage_message + )); + + return $o; + + } + + +} diff --git a/Zotlabs/Module/Nojs.php b/Zotlabs/Module/Nojs.php new file mode 100644 index 000000000..6fd6d8106 --- /dev/null +++ b/Zotlabs/Module/Nojs.php @@ -0,0 +1,15 @@ + 1) ? intval(argv(1)) : 1); + setcookie('jsdisabled', $n, 0, '/'); + $p = $_GET['redir']; + $hasq = strpos($p,'?'); + goaway(z_root() . (($p) ? '/' . $p : '') . (($hasq) ? '' : '?f=' ) . '&jsdisabled=' . $n); + + } +} diff --git a/Zotlabs/Module/Notes.php b/Zotlabs/Module/Notes.php new file mode 100644 index 000000000..e530e6ff4 --- /dev/null +++ b/Zotlabs/Module/Notes.php @@ -0,0 +1,40 @@ + true); + if(array_key_exists('note_text',$_REQUEST)) { + $body = escape_tags($_REQUEST['note_text']); + + // I've had my notes vanish into thin air twice in four years. + // Provide a backup copy if there were contents previously + // and there are none being saved now. + + if(! $body) { + $old_text = get_pconfig(local_channel(),'notes','text'); + if($old_text) + set_pconfig(local_channel(),'notes','text.bak',$old_text); + } + set_pconfig(local_channel(),'notes','text',$body); + } + + // push updates to channel clones + + if((argc() > 1) && (argv(1) === 'sync')) { + require_once('include/zot.php'); + build_sync_packet(); + } + + logger('notes saved.', LOGGER_DEBUG); + json_return_and_die($ret); + + } + +} diff --git a/Zotlabs/Module/Notifications.php b/Zotlabs/Module/Notifications.php new file mode 100644 index 000000000..9da28a360 --- /dev/null +++ b/Zotlabs/Module/Notifications.php @@ -0,0 +1,111 @@ + 1) ? \App::$argv[1] : 0); + + if($request_id === "all") + return; + + if($request_id) { + + $r = q("SELECT * FROM `intro` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($request_id), + intval(local_channel()) + ); + + if(count($r)) { + $intro_id = $r[0]['id']; + $contact_id = $r[0]['contact-id']; + } + else { + notice( t('Invalid request identifier.') . EOL); + return; + } + + // If it is a friend suggestion, the contact is not a new friend but an existing friend + // that should not be deleted. + + $fid = $r[0]['fid']; + + if($_POST['submit'] == t('Discard')) { + $r = q("DELETE FROM `intro` WHERE `id` = %d", + intval($intro_id) + ); + if(! $fid) { + + // The check for blocked and pending is in case the friendship was already approved + // and we just want to get rid of the now pointless notification + + $r = q("DELETE FROM `contact` WHERE `id` = %d AND `uid` = %d AND `self` = 0 AND `blocked` = 1 AND `pending` = 1", + intval($contact_id), + intval(local_channel()) + ); + } + goaway(z_root() . '/notifications/intros'); + } + if($_POST['submit'] == t('Ignore')) { + $r = q("UPDATE `intro` SET `ignore` = 1 WHERE `id` = %d", + intval($intro_id)); + goaway(z_root() . '/notifications/intros'); + } + } + } + + + + + + function get() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + nav_set_selected('notifications'); + + $o = ''; + + $notif_tpl = get_markup_template('notifications.tpl'); + + $not_tpl = get_markup_template('notify.tpl'); + require_once('include/bbcode.php'); + + $r = q("SELECT * from notify where uid = %d and seen = 0 order by created desc", + intval(local_channel()) + ); + + if ($r > 0) { + $notifications_available =1; + foreach ($r as $it) { + $notif_content .= replace_macros($not_tpl,array( + '$item_link' => z_root().'/notify/view/'. $it['id'], + '$item_image' => $it['photo'], + '$item_text' => strip_tags(bbcode($it['msg'])), + '$item_when' => relative_date($it['created']) + )); + } + } else { + $notif_content .= t('No more system notifications.'); + } + + $o .= replace_macros($notif_tpl,array( + '$notif_header' => t('System Notifications'), + '$notif_link_mark_seen' => t('Mark all system notifications seen'), + '$notif_content' => $notif_content, + '$notifications_available' => $notifications_available, + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Notify.php b/Zotlabs/Module/Notify.php new file mode 100644 index 000000000..f592f6f37 --- /dev/null +++ b/Zotlabs/Module/Notify.php @@ -0,0 +1,69 @@ + 2 && argv(1) === 'view' && intval(argv(2))) { + $r = q("select * from notify where id = %d and uid = %d limit 1", + intval(argv(2)), + intval(local_channel()) + ); + if($r) { + q("update notify set seen = 1 where (( parent != '' and parent = '%s' and otype = '%s' ) or link = '%s' ) and uid = %d", + dbesc($r[0]['parent']), + dbesc($r[0]['otype']), + dbesc($r[0]['link']), + intval(local_channel()) + ); + goaway($r[0]['link']); + } + goaway(z_root()); + } + + + } + + + function get() { + if(! local_channel()) + return login(); + + $notif_tpl = get_markup_template('notifications.tpl'); + + $not_tpl = get_markup_template('notify.tpl'); + require_once('include/bbcode.php'); + + $r = q("SELECT * from notify where uid = %d and seen = 0 order by created desc", + intval(local_channel()) + ); + + if($r) { + foreach ($r as $it) { + $notif_content .= replace_macros($not_tpl,array( + '$item_link' => z_root().'/notify/view/'. $it['id'], + '$item_image' => $it['photo'], + '$item_text' => strip_tags(bbcode($it['msg'])), + '$item_when' => relative_date($it['created']) + )); + } + } + else { + $notif_content .= t('No more system notifications.'); + } + + $o .= replace_macros($notif_tpl,array( + '$notif_header' => t('System Notifications'), + '$tabs' => '', // $tabs, + '$notif_content' => $notif_content, + )); + + return $o; + + } +} diff --git a/Zotlabs/Module/Oembed.php b/Zotlabs/Module/Oembed.php new file mode 100644 index 000000000..b02182053 --- /dev/null +++ b/Zotlabs/Module/Oembed.php @@ -0,0 +1,36 @@ + 1) { + if (argv(1) == 'b2h'){ + $url = array( "", trim(hex2bin($_REQUEST['url']))); + echo oembed_replacecb($url); + killme(); + } + + elseif (argv(1) == 'h2b'){ + $text = trim(hex2bin($_REQUEST['text'])); + echo oembed_html2bbcode($text); + killme(); + } + + else { + echo ""; + $src = base64url_decode(argv(1)); + $j = oembed_fetch_url($src); + echo $j->html; + // logger('mod-oembed ' . $h, LOGGER_ALL); + echo ""; + } + } + killme(); + } + +} diff --git a/Zotlabs/Module/Oep.php b/Zotlabs/Module/Oep.php new file mode 100644 index 000000000..dc0547a42 --- /dev/null +++ b/Zotlabs/Module/Oep.php @@ -0,0 +1,403 @@ + 1 && argv(1) === 'html') ? true : false); + if($_REQUEST['url']) { + $_REQUEST['url'] = strip_zids($_REQUEST['url']); + $url = $_REQUEST['url']; + } + + if(! $url) + http_status_exit(404, 'Not found'); + + $maxwidth = $_REQUEST['maxwidth']; + $maxheight = $_REQUEST['maxheight']; + $format = $_REQUEST['format']; + if($format && $format !== 'json') + http_status_exit(501, 'Not implemented'); + + if(fnmatch('*/photos/*/album/*',$url)) + $arr = $this->oep_album_reply($_REQUEST); + elseif(fnmatch('*/photos/*/image/*',$url)) + $arr = $this->oep_photo_reply($_REQUEST); + elseif(fnmatch('*/photos*',$url)) + $arr = $this->oep_phototop_reply($_REQUEST); + elseif(fnmatch('*/display/*',$url)) + $arr = $this->oep_display_reply($_REQUEST); + elseif(fnmatch('*/channel/*mid=*',$url)) + $arr = $this->oep_mid_reply($_REQUEST); + elseif(fnmatch('*/channel*',$url)) + $arr = $this->oep_profile_reply($_REQUEST); + elseif(fnmatch('*/profile/*',$url)) + $arr = $this->oep_profile_reply($_REQUEST); + + if($arr) { + if($html) { + if($arr['type'] === 'rich') { + header('Content-Type: text/html'); + echo $arr['html']; + } + } + else { + header('Content-Type: application/json+oembed'); + echo json_encode($arr); + } + killme(); + } + + http_status_exit(404,'Not found'); + + } + + function oep_display_reply($args) { + + $ret = array(); + $url = $args['url']; + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + if(preg_match('#//(.*?)/(.*?)/(.*?)/(.*?)mid\=(.*?)(&|$)#',$url,$matches)) { + $chn = $matches[3]; + $res = $matches[5]; + } + + if(! ($chn && $res)) + return; + $c = q("select * from channel where channel_address = '%s' limit 1", + dbesc($chn) + ); + + if(! $c) + return; + + $sql_extra = item_permissions_sql($c[0]['channel_id']); + + $p = q("select * from item where mid = '%s' and uid = %d $sql_extra limit 1", + dbesc($res), + intval($c[0]['channel_id']) + ); + if(! $p) + return; + + xchan_query($p,true); + $p = fetch_post_tags($p,true); + + $o = "[share author='".urlencode($p[0]['author']['xchan_name']). + "' profile='".$p[0]['author']['xchan_url'] . + "' avatar='".$p[0]['author']['xchan_photo_s']. + "' link='".$p[0]['plink']. + "' posted='".$p[0]['created']. + "' message_id='".$p[0]['mid']."']"; + if($p[0]['title']) + $o .= '[b]'.$p[0]['title'].'[/b]'."\r\n"; + $o .= $p[0]['body']; + $o .= "[/share]"; + $o = bbcode($o); + + $ret['type'] = 'rich'; + + $w = (($maxwidth) ? $maxwidth : 640); + $h = (($maxheight) ? $maxheight : $w * 2 / 3); + + $ret['html'] = '
    ' . $o . '
    '; + + $ret['width'] = $w; + $ret['height'] = $h; + + return $ret; + + } + + function oep_mid_reply($args) { + + $ret = array(); + $url = $args['url']; + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + if(preg_match('#//(.*?)/(.*?)/(.*?)/(.*?)mid\=(.*?)(&|$)#',$url,$matches)) { + $chn = $matches[3]; + $res = $matches[5]; + } + + if(! ($chn && $res)) + return; + $c = q("select * from channel where channel_address = '%s' limit 1", + dbesc($chn) + ); + + if(! $c) + return; + + $sql_extra = item_permissions_sql($c[0]['channel_id']); + + $p = q("select * from item where mid = '%s' and uid = %d $sql_extra limit 1", + dbesc($res), + intval($c[0]['channel_id']) + ); + if(! $p) + return; + + xchan_query($p,true); + $p = fetch_post_tags($p,true); + + $o = "[share author='".urlencode($p[0]['author']['xchan_name']). + "' profile='".$p[0]['author']['xchan_url'] . + "' avatar='".$p[0]['author']['xchan_photo_s']. + "' link='".$p[0]['plink']. + "' posted='".$p[0]['created']. + "' message_id='".$p[0]['mid']."']"; + if($p[0]['title']) + $o .= '[b]'.$p[0]['title'].'[/b]'."\r\n"; + $o .= $p[0]['body']; + $o .= "[/share]"; + $o = bbcode($o); + + $ret['type'] = 'rich'; + + $w = (($maxwidth) ? $maxwidth : 640); + $h = (($maxheight) ? $maxheight : $w * 2 / 3); + + $ret['html'] = '
    ' . $o . '
    '; + + $ret['width'] = $w; + $ret['height'] = $h; + + return $ret; + + } + + function oep_profile_reply($args) { + + + require_once('include/channel.php'); + + $url = $args['url']; + + if(preg_match('#//(.*?)/(.*?)/(.*?)(/|\?|&|$)#',$url,$matches)) { + $chn = $matches[3]; + } + + if(! $chn) + return; + + $c = channelx_by_nick($chn); + + if(! $c) + return; + + + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + $width = 800; + $height = 375; + + if($maxwidth) { + $width = $maxwidth; + $height = (375 / 800) * $width; + } + if($maxheight) { + if($maxheight < $height) { + $width = (800 / 375) * $maxheight; + $height = $maxheight; + } + } + $ret = array(); + + $ret['type'] = 'rich'; + $ret['width'] = intval($width); + $ret['height'] = intval($height); + + $ret['html'] = get_zcard_embed($c,get_observer_hash(),array('width' => $width, 'height' => $height)); + + return $ret; + + } + + function oep_album_reply($args) { + + $ret = array(); + $url = $args['url']; + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + if(preg_match('|//(.*?)/(.*?)/(.*?)/album/|',$url,$matches)) { + $chn = $matches[3]; + $res = hex2bin(basename($url)); + } + + if(! ($chn && $res)) + return; + $c = q("select * from channel where channel_address = '%s' limit 1", + dbesc($chn) + ); + + if(! $c) + return; + + $sql_extra = permissions_sql($c[0]['channel_id']); + + $p = q("select resource_id from photo where album = '%s' and uid = %d and imgscale = 0 $sql_extra order by created desc limit 1", + dbesc($res), + intval($c[0]['channel_id']) + ); + if(! $p) + return; + + $res = $p[0]['resource_id']; + + $r = q("select height, width, imgscale, resource_id from photo where uid = %d and resource_id = '%s' $sql_extra order by imgscale asc", + intval($c[0]['channel_id']), + dbesc($res) + ); + + if($r) { + foreach($r as $rr) { + $foundres = false; + if($maxheight && $rr['height'] > $maxheight) + continue; + if($maxwidth && $rr['width'] > $maxwidth) + continue; + $foundres = true; + break; + } + + if($foundres) { + $ret['type'] = 'link'; + $ret['thumbnail_url'] = z_root() . '/photo/' . '/' . $rr['resource_id'] . '-' . $rr['imgscale']; + $ret['thumbnail_width'] = $rr['width']; + $ret['thumbnail_height'] = $rr['height']; + } + + + } + return $ret; + + } + + + function oep_phototop_reply($args) { + + $ret = array(); + $url = $args['url']; + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + if(preg_match('|//(.*?)/(.*?)/(.*?)$|',$url,$matches)) { + $chn = $matches[3]; + } + + if(! $chn) + return; + $c = q("select * from channel where channel_address = '%s' limit 1", + dbesc($chn) + ); + + if(! $c) + return; + + $sql_extra = permissions_sql($c[0]['channel_id']); + + $p = q("select resource_id from photo where uid = %d and imgscale = 0 $sql_extra order by created desc limit 1", + intval($c[0]['channel_id']) + ); + if(! $p) + return; + + $res = $p[0]['resource_id']; + + $r = q("select height, width, imgscale, resource_id from photo where uid = %d and resource_id = '%s' $sql_extra order by imgscale asc", + intval($c[0]['channel_id']), + dbesc($res) + ); + + if($r) { + foreach($r as $rr) { + $foundres = false; + if($maxheight && $rr['height'] > $maxheight) + continue; + if($maxwidth && $rr['width'] > $maxwidth) + continue; + $foundres = true; + break; + } + + if($foundres) { + $ret['type'] = 'link'; + $ret['thumbnail_url'] = z_root() . '/photo/' . '/' . $rr['resource_id'] . '-' . $rr['imgscale']; + $ret['thumbnail_width'] = $rr['width']; + $ret['thumbnail_height'] = $rr['height']; + } + + + } + return $ret; + + } + + + function oep_photo_reply($args) { + + $ret = array(); + $url = $args['url']; + $maxwidth = intval($args['maxwidth']); + $maxheight = intval($args['maxheight']); + + if(preg_match('|//(.*?)/(.*?)/(.*?)/image/|',$url,$matches)) { + $chn = $matches[3]; + $res = basename($url); + } + + if(! ($chn && $res)) + return; + $c = q("select * from channel where channel_address = '%s' limit 1", + dbesc($chn) + ); + + if(! $c) + return; + + $sql_extra = permissions_sql($c[0]['channel_id']); + + + $r = q("select height, width, imgscale, resource_id from photo where uid = %d and resource_id = '%s' $sql_extra order by imgscale asc", + intval($c[0]['channel_id']), + dbesc($res) + ); + + if($r) { + foreach($r as $rr) { + $foundres = false; + if($maxheight && $rr['height'] > $maxheight) + continue; + if($maxwidth && $rr['width'] > $maxwidth) + continue; + $foundres = true; + break; + } + + if($foundres) { + $ret['type'] = 'link'; + $ret['thumbnail_url'] = z_root() . '/photo/' . '/' . $rr['resource_id'] . '-' . $rr['imgscale']; + $ret['thumbnail_width'] = $rr['width']; + $ret['thumbnail_height'] = $rr['height']; + } + + + } + return $ret; + + } +} diff --git a/Zotlabs/Module/Oexchange.php b/Zotlabs/Module/Oexchange.php new file mode 100644 index 000000000..24fc14821 --- /dev/null +++ b/Zotlabs/Module/Oexchange.php @@ -0,0 +1,77 @@ + 1) && (argv(1) === 'xrd')) { + $tpl = get_markup_template('oexchange_xrd.tpl'); + + $o = replace_macros($tpl, array('$base' => z_root())); + echo $o; + killme(); + } + } + + function get() { + + if(! local_channel()) { + if(remote_channel()) { + $observer = \App::get_observer(); + if($observer && $observer['xchan_url']) { + $parsed = @parse_url($observer['xchan_url']); + if(! $parsed) { + notice( t('Unable to find your hub.') . EOL); + return; + } + $url = $parsed['scheme'] . '://' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : ''); + $url .= '/oexchange'; + $result = z_post_url($url,$_REQUEST); + json_return_and_die($result); + } + } + + return login(false); + } + + if((argc() > 1) && argv(1) === 'done') { + info( t('Post successful.') . EOL); + return; + } + + $url = (((x($_REQUEST,'url')) && strlen($_REQUEST['url'])) + ? urlencode(notags(trim($_REQUEST['url']))) : ''); + $title = (((x($_REQUEST,'title')) && strlen($_REQUEST['title'])) + ? '&title=' . urlencode(notags(trim($_REQUEST['title']))) : ''); + $description = (((x($_REQUEST,'description')) && strlen($_REQUEST['description'])) + ? '&description=' . urlencode(notags(trim($_REQUEST['description']))) : ''); + $tags = (((x($_REQUEST,'tags')) && strlen($_REQUEST['tags'])) + ? '&tags=' . urlencode(notags(trim($_REQUEST['tags']))) : ''); + + $ret = z_fetch_url(z_root() . '/urlinfo?f=&url=' . $url . $title . $description . $tags); + + if($ret['success']) + $s = $ret['body']; + + if(! strlen($s)) + return; + + $post = array(); + + $post['profile_uid'] = local_channel(); + $post['return'] = '/oexchange/done' ; + $post['body'] = $s; + $post['type'] = 'wall'; + + $_REQUEST = $post; + $mod = new Item(); + $mod->post(); + + } + + + +} diff --git a/Zotlabs/Module/Online.php b/Zotlabs/Module/Online.php new file mode 100644 index 000000000..39399db38 --- /dev/null +++ b/Zotlabs/Module/Online.php @@ -0,0 +1,17 @@ + false); + if(argc() != 2) + json_return_and_die($ret); + + $ret = get_online_status(argv(1)); + json_return_and_die($ret); + } + +} diff --git a/Zotlabs/Module/Openid.php b/Zotlabs/Module/Openid.php new file mode 100644 index 000000000..8cbc6d2fd --- /dev/null +++ b/Zotlabs/Module/Openid.php @@ -0,0 +1,198 @@ +validate()) { + + logger('openid: validate'); + + $authid = normalise_openid($_REQUEST['openid_identity']); + + if(! strlen($authid)) { + logger( t('OpenID protocol error. No ID returned.') . EOL); + goaway(z_root()); + } + + $x = match_openid($authid); + if($x) { + + $r = q("select * from channel where channel_id = %d limit 1", + intval($x) + ); + if($r) { + $y = q("select * from account where account_id = %d limit 1", + intval($r[0]['channel_account_id']) + ); + if($y) { + foreach($y as $record) { + if(($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED)) { + logger('mod_openid: openid success for ' . $x[0]['channel_name']); + $_SESSION['uid'] = $r[0]['channel_id']; + $_SESSION['account_id'] = $r[0]['channel_account_id']; + $_SESSION['authenticated'] = true; + authenticate_success($record,$r[0],true,true,true,true); + goaway(z_root()); + } + } + } + } + } + + // Successful OpenID login - but we can't match it to an existing account. + // See if they've got an xchan + + $r = q("select * from xconfig left join xchan on xchan_hash = xconfig.xchan where cat = 'system' and k = 'openid' and v = '%s' limit 1", + dbesc($authid) + ); + + if($r) { + $_SESSION['authenticated'] = 1; + $_SESSION['visitor_id'] = $r[0]['xchan_hash']; + $_SESSION['my_url'] = $r[0]['xchan_url']; + $_SESSION['my_address'] = $r[0]['xchan_addr']; + $arr = array('xchan' => $r[0], 'session' => $_SESSION); + call_hooks('magic_auth_openid_success',$arr); + \App::set_observer($r[0]); + require_once('include/security.php'); + \App::set_groups(init_groups_visitor($_SESSION['visitor_id'])); + info(sprintf( t('Welcome %s. Remote authentication successful.'),$r[0]['xchan_name'])); + logger('mod_openid: remote auth success from ' . $r[0]['xchan_addr']); + if($_SESSION['return_url']) + goaway($_SESSION['return_url']); + goaway(z_root()); + } + + // no xchan... + // create one. + // We should probably probe the openid url and figure out if they have any kind of social presence we might be able to + // scrape some identifying info from. + + $name = $authid; + $url = trim($_REQUEST['openid_identity'],'/'); + if(strpos($url,'http') === false) + $url = 'https://' . $url; + $pphoto = z_root() . '/' . get_default_profile_photo(); + $parsed = @parse_url($url); + if($parsed) { + $host = $parsed['host']; + } + + $attr = $openid->getAttributes(); + + if(is_array($attr) && count($attr)) { + foreach($attr as $k => $v) { + if($k === 'namePerson/friendly') + $nick = notags(trim($v)); + if($k === 'namePerson/first') + $first = notags(trim($v)); + if($k === 'namePerson') + $name = notags(trim($v)); + if($k === 'contact/email') + $addr = notags(trim($v)); + if($k === 'media/image/aspect11') + $photosq = trim($v); + if($k === 'media/image/default') + $photo_other = trim($v); + } + } + if(! $nick) { + if($first) + $nick = $first; + else + $nick = $name; + } + + require_once('library/urlify/URLify.php'); + $x = strtolower(\URLify::transliterate($nick)); + if($nick & $host) + $addr = $nick . '@' . $host; + $network = 'unknown'; + + if($photosq) + $pphoto = $photosq; + elseif($photo_other) + $pphoto = $photo_other; + + $mimetype = guess_image_type($pphoto); + + $x = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_mimetype, + xchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_follow, xchan_connpage, xchan_name, xchan_network, xchan_photo_date, + xchan_name_date, xchan_hidden) + values ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', 1) ", + dbesc($url), + dbesc(''), + dbesc(''), + dbesc(''), + dbesc($mimetype), + dbesc($pphoto), + dbesc($addr), + dbesc($url), + dbesc(''), + dbesc(''), + dbesc(''), + dbesc($name), + dbesc($network), + dbesc(datetime_convert()), + dbesc(datetime_convert()) + ); + if($x) { + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($url) + ); + if($r) { + + $photos = import_xchan_photo($pphoto,$url); + if($photos) { + $z = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', + xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", + dbesc(datetime_convert()), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($url) + ); + } + + set_xconfig($url,'system','openid',$authid); + $_SESSION['authenticated'] = 1; + $_SESSION['visitor_id'] = $r[0]['xchan_hash']; + $_SESSION['my_url'] = $r[0]['xchan_url']; + $_SESSION['my_address'] = $r[0]['xchan_addr']; + $arr = array('xchan' => $r[0], 'session' => $_SESSION); + call_hooks('magic_auth_openid_success',$arr); + \App::set_observer($r[0]); + info(sprintf( t('Welcome %s. Remote authentication successful.'),$r[0]['xchan_name'])); + logger('mod_openid: remote auth success from ' . $r[0]['xchan_addr']); + if($_SESSION['return_url']) + goaway($_SESSION['return_url']); + goaway(z_root()); + } + } + + } + } + notice( t('Login failed.') . EOL); + goaway(z_root()); + // NOTREACHED + } + +} diff --git a/Zotlabs/Module/Opensearch.php b/Zotlabs/Module/Opensearch.php new file mode 100644 index 000000000..8e76038c9 --- /dev/null +++ b/Zotlabs/Module/Opensearch.php @@ -0,0 +1,24 @@ + z_root(), + '$nodename' => \App::get_hostname(), + )); + + echo $o; + + killme(); + + } + +} diff --git a/Zotlabs/Module/Page.php b/Zotlabs/Module/Page.php new file mode 100644 index 000000000..6ef285dd0 --- /dev/null +++ b/Zotlabs/Module/Page.php @@ -0,0 +1,148 @@ +parse($r[0]['body']); + \App::$pdl = $r[0]['body']; + } + elseif($r[0]['layout_mid']) { + $l = q("select body from item where mid = '%s' and uid = %d limit 1", + dbesc($r[0]['layout_mid']), + intval($u[0]['channel_id']) + ); + + if($l) { + \App::$comanche = new \Zotlabs\Render\Comanche(); + \App::$comanche->parse($l[0]['body']); + \App::$pdl = $l[0]['body']; + } + } + + \App::$data['webpage'] = $r; + + } + + function get() { + + $r = \App::$data['webpage']; + if(! $r) + return; + + if($r[0]['item_type'] == ITEM_TYPE_PDL) { + $r[0]['body'] = t('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'); + $r[0]['mimetype'] = 'text/plain'; + $r[0]['title'] = ''; + + } + + xchan_query($r); + $r = fetch_post_tags($r,true); + + if($r[0]['mimetype'] === 'application/x-pdl') + \App::$page['pdl_content'] = true; + + $o .= prepare_page($r[0]); + return $o; + + } + +} diff --git a/Zotlabs/Module/Pconfig.php b/Zotlabs/Module/Pconfig.php new file mode 100644 index 000000000..b6264bddc --- /dev/null +++ b/Zotlabs/Module/Pconfig.php @@ -0,0 +1,122 @@ +disallowed_pconfig())) { + notice( t('This setting requires special processing and editing has been blocked.') . EOL); + return; + } + + if(strpos($k,'password') !== false) { + $v = z_obscure($v); + } + + set_pconfig(local_channel(),$cat,$k,$v); + build_sync_packet(); + + goaway(z_root() . '/pconfig/' . $cat . '/' . $k); + + } + + + function get() { + + if(! local_channel()) { + return login(); + } + + $content = '

    ' . t('Configuration Editor') . '

    '; + $content .= '
    ' . t('Warning: Changing some settings could render your channel inoperable. Please leave this page unless you are comfortable with and knowledgeable about how to correctly use this feature.') . '
    ' . EOL . EOL; + + + + if(argc() == 3) { + $content .= 'pconfig[' . local_channel() . ']' . EOL; + $content .= 'pconfig[' . local_channel() . '][' . escape_tags(argv(1)) . ']' . EOL . EOL; + $content .= 'pconfig[' . local_channel() . '][' . escape_tags(argv(1)) . '][' . escape_tags(argv(2)) . '] = ' . get_pconfig(local_channel(),escape_tags(argv(1)),escape_tags(argv(2))) . EOL; + + if(in_array(argv(2),$this->disallowed_pconfig())) { + notice( t('This setting requires special processing and editing has been blocked.') . EOL); + return $content; + } + else + $content .= $this->pconfig_form(escape_tags(argv(1)),escape_tags(argv(2))); + } + + + if(argc() == 2) { + $content .= 'pconfig[' . local_channel() . ']' . EOL; + load_pconfig(local_channel(),escape_tags(argv(1))); + foreach(\App::$config[local_channel()][escape_tags(argv(1))] as $k => $x) { + $content .= 'pconfig[' . local_channel() . '][' . escape_tags(argv(1)) . '][' . $k . '] = ' . escape_tags($x) . EOL; + } + } + + if(argc() == 1) { + + $r = q("select * from pconfig where uid = " . local_channel()); + if($r) { + foreach($r as $rr) { + $content .= 'pconfig[' . local_channel() . '][' . escape_tags($rr['cat']) . '][' . escape_tags($rr['k']) . '] = ' . escape_tags($rr['v']) . EOL; + } + } + } + return $content; + + } + + + function pconfig_form($cat,$k) { + + $o = '
    '; + $o .= ''; + + $v = get_pconfig(local_channel(),$cat,$k); + if(strpos($k,'password') !== false) + $v = z_unobscure($v); + + $o .= ''; + $o .= ''; + + if(strpos($v,"\n")) + $o .= ''; + else + $o .= ''; + + $o .= EOL . EOL; + $o .= ''; + $o .= '
    '; + + return $o; + + } + + + + function disallowed_pconfig() { + return array( + 'permissions_role' + ); + } + +} diff --git a/Zotlabs/Module/Pdledit.php b/Zotlabs/Module/Pdledit.php new file mode 100644 index 000000000..5cb00f165 --- /dev/null +++ b/Zotlabs/Module/Pdledit.php @@ -0,0 +1,72 @@ + 1) + $module = 'mod_' . argv(1) . '.pdl'; + else { + $o .= '
    '; + $o .= '

    ' . t('Edit System Page Description') . '

    '; + $files = glob('Zotlabs/Module/*.php'); + if($files) { + foreach($files as $f) { + $name = lcfirst(basename($f,'.php')); + $x = theme_include('mod_' . $name . '.pdl'); + if($x) { + $o .= '' . $name . '
    '; + } + } + } + + $o .= '
    '; + + // list module pdl files + return $o; + } + + $t = get_pconfig(local_channel(),'system',$module); + if(! $t) + $t = file_get_contents(theme_include($module)); + if(! $t) { + notice( t('Layout not found.') . EOL); + return ''; + } + + $o = replace_macros(get_markup_template('pdledit.tpl'),array( + '$header' => t('Edit System Page Description'), + '$mname' => t('Module Name:'), + '$help' => t('Layout Help'), + '$module' => argv(1), + '$content' => htmlspecialchars($t,ENT_COMPAT,'UTF-8'), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Photo.php b/Zotlabs/Module/Photo.php new file mode 100644 index 000000000..66aaec49f --- /dev/null +++ b/Zotlabs/Module/Photo.php @@ -0,0 +1,274 @@ + 1)) + { + $resolution = 1; + } + } + + // If using resolution 1, make sure it exists before proceeding: + if ($resolution == 1) + { + $r = q("SELECT uid FROM photo WHERE resource_id = '%s' AND imgscale = %d LIMIT 1", + dbesc($photo), + intval($resolution) + ); + if (!($r)) + $resolution = 2; + } + + $r = q("SELECT uid FROM photo WHERE resource_id = '%s' AND imgscale = %d LIMIT 1", + dbesc($photo), + intval($resolution) + ); + if($r) { + + $allowed = (($r[0]['uid']) ? perm_is_allowed($r[0]['uid'],$observer_xchan,'view_storage') : true); + + $sql_extra = permissions_sql($r[0]['uid']); + + $channel = channelx_by_n($r[0]['uid']); + + // Now we'll see if we can access the photo + + $r = q("SELECT * FROM photo WHERE resource_id = '%s' AND imgscale = %d $sql_extra LIMIT 1", + dbesc($photo), + intval($resolution) + ); + + if($r && $allowed) { + $data = dbunescbin($r[0]['content']); + $mimetype = $r[0]['mimetype']; + if(intval($r[0]['os_storage'])) { + $streaming = $data; + } + } + else { + + // Does the picture exist? It may be a remote person with no credentials, + // but who should otherwise be able to view it. Show a default image to let + // them know permissions was denied. It may be possible to view the image + // through an authenticated profile visit. + // There won't be many completely unauthorised people seeing this because + // they won't have the photo link, so there's a reasonable chance that the person + // might be able to obtain permission to view it. + + $r = q("SELECT * FROM `photo` WHERE `resource_id` = '%s' AND `imgscale` = %d LIMIT 1", + dbesc($photo), + intval($resolution) + ); + + if($r) { + logger('mod_photo: forbidden. ' . \App::$query_string); + $observer = \App::get_observer(); + logger('mod_photo: observer = ' . (($observer) ? $observer['xchan_addr'] : '(not authenticated)')); + $data = file_get_contents('images/nosign.png'); + $mimetype = 'image/png'; + $prvcachecontrol = true; + } + } + } + } + + if(! isset($data)) { + if(isset($resolution)) { + switch($resolution) { + + case 4: + $data = file_get_contents(get_default_profile_photo()); + $mimetype = 'image/png'; + break; + case 5: + $data = file_get_contents(get_default_profile_photo(80)); + $mimetype = 'image/png'; + break; + case 6: + $data = file_get_contents(get_default_profile_photo(48)); + $mimetype = 'image/png'; + break; + default: + killme(); + // NOTREACHED + break; + } + } + } + + if(isset($res) && intval($res) && $res < 500) { + $ph = photo_factory($data, $mimetype); + if($ph->is_valid()) { + $ph->scaleImageSquare($res); + $data = $ph->imageString(); + $mimetype = $ph->getType(); + } + } + + // Writing in cachefile + if (isset($cachefile) && $cachefile != '') + file_put_contents($cachefile, $data); + + if(function_exists('header_remove')) { + header_remove('Pragma'); + header_remove('pragma'); + } + + header("Content-type: " . $mimetype); + + if($prvcachecontrol) { + + // it is a private photo that they have no permission to view. + // tell the browser not to cache it, in case they authenticate + // and subsequently have permission to see it + + header("Cache-Control: no-store, no-cache, must-revalidate"); + + } + else { + // The photo cache default is 1 day to provide a privacy trade-off, + // as somebody reducing photo permissions on a photo that is already + // "in the wild" won't be able to stop the photo from being viewed + // for this amount amount of time once it is in the browser cache. + // The privacy expectations of your site members and their perception + // of privacy where it affects the entire project may be affected. + // This has performance considerations but we highly recommend you + // leave it alone. + + $cache = get_config('system','photo_cache_time'); + if(! $cache) + $cache = (3600 * 24); // 1 day + + header("Expires: " . gmdate("D, d M Y H:i:s", time() + $cache) . " GMT"); + header("Cache-Control: max-age=" . $cache); + + } + + // If it's a file resource, stream it. + + if($streaming && $channel) { + if(strpos($streaming,'store') !== false) + $istream = fopen($streaming,'rb'); + else + $istream = fopen('store/' . $channel['channel_address'] . '/' . $streaming,'rb'); + $ostream = fopen('php://output','wb'); + if($istream && $ostream) { + pipe_streams($istream,$ostream); + fclose($istream); + fclose($ostream); + } + } + else { + echo $data; + } + + killme(); + // NOTREACHED + } + +} diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php new file mode 100644 index 000000000..1eeab1461 --- /dev/null +++ b/Zotlabs/Module/Photos.php @@ -0,0 +1,1381 @@ + 1) { + $nick = argv(1); + + profile_load($nick); + + $channelx = channelx_by_nick($nick); + + if(! $channelx) + return; + + \App::$data['channel'] = $channelx; + + $observer = \App::get_observer(); + \App::$data['observer'] = $observer; + + $observer_xchan = (($observer) ? $observer['xchan_hash'] : ''); + + head_set_icon(\App::$data['channel']['xchan_photo_s']); + + \App::$page['htmlhead'] .= "" ; + + } + + return; + } + + + + function post() { + + logger('mod-photos: photos_post: begin' , LOGGER_DEBUG); + + + logger('mod_photos: REQUEST ' . print_r($_REQUEST,true), LOGGER_DATA); + logger('mod_photos: FILES ' . print_r($_FILES,true), LOGGER_DATA); + + $ph = photo_factory(''); + + $phototypes = $ph->supportedTypes(); + + $can_post = false; + + $page_owner_uid = \App::$data['channel']['channel_id']; + + if(perm_is_allowed($page_owner_uid,get_observer_hash(),'write_storage')) + $can_post = true; + + if(! $can_post) { + notice( t('Permission denied.') . EOL ); + if(is_ajax()) + killme(); + return; + } + + $s = abook_self($page_owner_uid); + + if(! $s) { + notice( t('Page owner information could not be retrieved.') . EOL); + logger('mod_photos: post: unable to locate contact record for page owner. uid=' . $page_owner_uid); + if(is_ajax()) + killme(); + return; + } + + $owner_record = $s[0]; + + $acl = new \Zotlabs\Access\AccessList(\App::$data['channel']); + + if((argc() > 3) && (argv(2) === 'album')) { + + $album = hex2bin(argv(3)); + + if($album === t('Profile Photos')) { + // not allowed + goaway(z_root() . '/' . $_SESSION['photo_return']); + } + + if(! photos_album_exists($page_owner_uid,$album)) { + notice( t('Album not found.') . EOL); + goaway(z_root() . '/' . $_SESSION['photo_return']); + } + + + /* + * RENAME photo album + */ + + $newalbum = notags(trim($_REQUEST['albumname'])); + if($newalbum != $album) { + + // @fixme - syncronise with DAV or disallow completely + + goaway(z_root() . '/' . $_SESSION['photo_return']); + + // $x = photos_album_rename($page_owner_uid,$album,$newalbum); + // if($x) { + // $newurl = str_replace(bin2hex($album),bin2hex($newalbum),$_SESSION['photo_return']); + // goaway(z_root() . '/' . $newurl); + // } + } + + /* + * DELETE photo album and all its photos + */ + + if($_REQUEST['dropalbum'] == t('Delete Album')) { + + + // This is dangerous because we combined file storage and photos into one interface + // This function will remove all photos from any directory with the same name since + // we have not passed the path value. + + // The correct solution would be to use a full pathname from your storage root for 'album' + // We also need to prevent/block removing the storage root folder. + + $folder_hash = ''; + + $r = q("select * from attach where is_dir = 1 and uid = %d and filename = '%s'", + intval($page_owner_uid), + dbesc($album) + ); + if(! $r) { + notice( t('Album not found.') . EOL); + return; + } + if(count($r) > 1) { + notice( t('Multiple storage folders exist with this album name, but within different directories. Please remove the desired folder or folders using the Files manager') . EOL); + return; + } + else { + $folder_hash = $r[0]['hash']; + } + + + + $res = array(); + + // get the list of photos we are about to delete + + if(remote_channel() && (! local_channel())) { + $str = photos_album_get_db_idstr($page_owner_uid,$album,remote_channel()); + } + elseif(local_channel()) { + $str = photos_album_get_db_idstr(local_channel(),$album); + } + else { + $str = null; + } + if(! $str) { + goaway(z_root() . '/' . $_SESSION['photo_return']); + } + + $r = q("select id from item where resource_id in ( $str ) and resource_type = 'photo' and uid = %d " . item_normal(), + intval($page_owner_uid) + ); + if($r) { + foreach($r as $i) { + attach_delete($page_owner_uid, $i['resource_id'], 1 ); + } + } + + // remove the associated photos in case they weren't attached to an item + + q("delete from photo where resource_id in ( $str ) and uid = %d", + intval($page_owner_uid) + ); + + // @FIXME do the same for the linked attach + + if($folder_hash) { + attach_delete($page_owner_uid,$folder_hash, 1); + + $sync = attach_export_data(\App::$data['channel'],$folder_hash, true); + + if($sync) + build_sync_packet($page_owner_uid,array('file' => array($sync))); + } + + } + + goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address']); + } + + if((argc() > 2) && (x($_REQUEST,'delete')) && ($_REQUEST['delete'] === t('Delete Photo'))) { + + // same as above but remove single photo + + $ob_hash = get_observer_hash(); + if(! $ob_hash) + goaway(z_root() . '/' . $_SESSION['photo_return']); + + $r = q("SELECT `id`, `resource_id` FROM `photo` WHERE ( xchan = '%s' or `uid` = %d ) AND `resource_id` = '%s' LIMIT 1", + dbesc($ob_hash), + intval(local_channel()), + dbesc(\App::$argv[2]) + ); + + if($r) { + attach_delete($page_owner_uid, $r[0]['resource_id'], 1 ); + $sync = attach_export_data(\App::$data['channel'],$r[0]['resource_id'], true); + + if($sync) + build_sync_packet($page_owner_uid,array('file' => array($sync))); + } + + goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . $_SESSION['album_return']); + } + + + if((\App::$argc > 2) && ((x($_POST,'desc') !== false) || (x($_POST,'newtag') !== false)) || (x($_POST,'albname') !== false)) { + + + $desc = ((x($_POST,'desc')) ? notags(trim($_POST['desc'])) : ''); + $rawtags = ((x($_POST,'newtag')) ? notags(trim($_POST['newtag'])) : ''); + $item_id = ((x($_POST,'item_id')) ? intval($_POST['item_id']) : 0); + $albname = ((x($_POST,'albname')) ? notags(trim($_POST['albname'])) : ''); + $is_nsfw = ((x($_POST,'adult')) ? intval($_POST['adult']) : 0); + + $acl->set_from_array($_POST); + $perm = $acl->get(); + + $resource_id = argv(2); + + if(! strlen($albname)) + $albname = datetime_convert('UTC',date_default_timezone_get(),'now', 'Y'); + + + if((x($_POST,'rotate') !== false) && + ( (intval($_POST['rotate']) == 1) || (intval($_POST['rotate']) == 2) )) { + logger('rotate'); + + $r = q("select * from photo where `resource_id` = '%s' and uid = %d and imgscale = 0 limit 1", + dbesc($resource_id), + intval($page_owner_uid) + ); + if(count($r)) { + $d = (($r[0]['os_storage']) ? @file_get_contents($r[0]['content']) : dbunescbin($r[0]['content'])); + $ph = photo_factory($d, $r[0]['mimetype']); + if($ph->is_valid()) { + $rotate_deg = ( (intval($_POST['rotate']) == 1) ? 270 : 90 ); + $ph->rotate($rotate_deg); + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + if(intval($r[0]['os_storage'])) { + @file_put_contents($r[0]['content'],$ph->imageString()); + $data = $r[0]['content']; + $fsize = @filesize($r[0]['content']); + q("update attach set filesize = %d where hash = '%s' and uid = %d limit 1", + intval($fsize), + dbesc($resource_id), + intval($page_owner_uid) + ); + } + else { + $data = $ph->imageString(); + $fsize = strlen($data); + } + + $x = q("update photo set content = '%s', filesize = %d, height = %d, width = %d where `resource_id` = '%s' and uid = %d and imgscale = 0", + dbescbin($data), + intval($fsize), + intval($height), + intval($width), + dbesc($resource_id), + intval($page_owner_uid) + ); + + if($width > 1024 || $height > 1024) + $ph->scaleImage(1024); + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + $x = q("update photo set content = '%s', height = %d, width = %d where `resource_id` = '%s' and uid = %d and imgscale = 1", + dbescbin($ph->imageString()), + intval($height), + intval($width), + dbesc($resource_id), + intval($page_owner_uid) + ); + + + if($width > 640 || $height > 640) + $ph->scaleImage(640); + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + $x = q("update photo set content = '%s', height = %d, width = %d where `resource_id` = '%s' and uid = %d and imgscale = 2", + dbescbin($ph->imageString()), + intval($height), + intval($width), + dbesc($resource_id), + intval($page_owner_uid) + ); + + + if($width > 320 || $height > 320) + $ph->scaleImage(320); + + $width = $ph->getWidth(); + $height = $ph->getHeight(); + + $x = q("update photo set content = '%s', height = %d, width = %d where `resource_id` = '%s' and uid = %d and imgscale = 3", + dbescbin($ph->imageString()), + intval($height), + intval($width), + dbesc($resource_id), + intval($page_owner_uid) + ); + } + } + } + + $p = q("SELECT mimetype, is_nsfw, description, resource_id, imgscale, allow_cid, allow_gid, deny_cid, deny_gid FROM photo WHERE resource_id = '%s' AND uid = %d ORDER BY imgscale DESC", + dbesc($resource_id), + intval($page_owner_uid) + ); + if($p) { + $ext = $phototypes[$p[0]['mimetype']]; + + $r = q("UPDATE `photo` SET `description` = '%s', `allow_cid` = '%s', `allow_gid` = '%s', `deny_cid` = '%s', `deny_gid` = '%s' WHERE `resource_id` = '%s' AND `uid` = %d", + dbesc($desc), + dbesc($perm['allow_cid']), + dbesc($perm['allow_gid']), + dbesc($perm['deny_cid']), + dbesc($perm['deny_gid']), + dbesc($resource_id), + intval($page_owner_uid) + ); + } + + $item_private = (($str_contact_allow || $str_group_allow || $str_contact_deny || $str_group_deny) ? true : false); + + $old_is_nsfw = $p[0]['is_nsfw']; + if($old_is_nsfw != $is_nsfw) { + $r = q("update photo set is_nsfw = %d where resource_id = '%s' and uid = %d", + intval($is_nsfw), + dbesc($resource_id), + intval($page_owner_uid) + ); + } + + /* Don't make the item visible if the only change was the album name */ + + $visibility = 0; + if($p[0]['description'] !== $desc || strlen($rawtags)) + $visibility = 1; + + if(! $item_id) { + $item_id = photos_create_item(\App::$data['channel'],get_observer_hash(),$p[0],$visibility); + + } + + if($item_id) { + $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item_id), + intval($page_owner_uid) + ); + + if($r) { + $old_tag = $r[0]['tag']; + $old_inform = $r[0]['inform']; + } + } + + + // make sure the linked item has the same permissions as the photo regardless of any other changes + $x = q("update item set allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d + where id = %d", + dbesc($perm['allow_cid']), + dbesc($perm['allow_gid']), + dbesc($perm['deny_cid']), + dbesc($perm['deny_gid']), + intval($acl->is_private()), + intval($item_id) + ); + + // make sure the attach has the same permissions as the photo regardless of any other changes + $x = q("update attach set allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where hash = '%s' and uid = %d and is_photo = 1", + dbesc($perm['allow_cid']), + dbesc($perm['allow_gid']), + dbesc($perm['deny_cid']), + dbesc($perm['deny_gid']), + dbesc($resource_id), + intval($page_owner_uid) + ); + + + + if(strlen($rawtags)) { + + $str_tags = ''; + $inform = ''; + + // if the new tag doesn't have a namespace specifier (@foo or #foo) give it a mention + + $x = substr($rawtags,0,1); + if($x !== '@' && $x !== '#') + $rawtags = '@' . $rawtags; + + require_once('include/text.php'); + $profile_uid = \App::$profile['profile_uid']; + + $results = linkify_tags($a, $rawtags, (local_channel()) ? local_channel() : $profile_uid); + + $success = $results['success']; + $post_tags = array(); + + foreach($results as $result) { + $success = $result['success']; + if($success['replaced']) { + $post_tags[] = array( + 'uid' => $profile_uid, + 'ttype' => $success['termtype'], + 'otype' => TERM_OBJ_POST, + 'term' => $success['term'], + 'url' => $success['url'] + ); + } + } + + $r = q("select * from item where id = %d and uid = %d limit 1", + intval($item_id), + intval($page_owner_uid) + ); + + if($r) { + $r = fetch_post_tags($r,true); + $datarray = $r[0]; + if($post_tags) { + if((! array_key_exists('term',$datarray)) || (! is_array($datarray['term']))) + $datarray['term'] = $post_tags; + else + $datarray['term'] = array_merge($datarray['term'],$post_tags); + } + item_store_update($datarray,$execflag); + } + + } + + goaway(z_root() . '/' . $_SESSION['photo_return']); + return; // NOTREACHED + + $sync = attach_export_data(\App::$data['channel'],$resource_id); + + if($sync) + build_sync_packet($page_owner_uid,array('file' => array($sync))); + + } + + + /** + * default post action - upload a photo + */ + + $channel = \App::$data['channel']; + $observer = \App::$data['observer']; + + $_REQUEST['source'] = 'photos'; + require_once('include/attach.php'); + + if(! local_channel()) { + $_REQUEST['contact_allow'] = expand_acl($channel['channel_allow_cid']); + $_REQUEST['group_allow'] = expand_acl($channel['channel_allow_gid']); + $_REQUEST['contact_deny'] = expand_acl($channel['channel_deny_cid']); + $_REQUEST['group_deny'] = expand_acl($channel['channel_deny_gid']); + } + + $r = attach_store($channel,get_observer_hash(), '', $_REQUEST); + + if(! $r['success']) { + notice($r['message'] . EOL); + } + + if($_REQUEST['newalbum']) + goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . bin2hex($_REQUEST['newalbum'])); + else + goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . bin2hex(datetime_convert('UTC',date_default_timezone_get(),'now', 'Y'))); + + } + + + + function get() { + + // URLs: + // photos/name + // photos/name/album/xxxxx (xxxxx is album name) + // photos/name/image/xxxxx + + + if(observer_prohibited()) { + notice( t('Public access denied.') . EOL); + return; + } + + $unsafe = ((array_key_exists('unsafe',$_REQUEST) && $_REQUEST['unsafe']) ? 1 : 0); + + require_once('include/bbcode.php'); + require_once('include/security.php'); + require_once('include/conversation.php'); + + if(! x(\App::$data,'channel')) { + notice( t('No photos selected') . EOL ); + return; + } + + $ph = photo_factory(''); + $phototypes = $ph->supportedTypes(); + + $_SESSION['photo_return'] = \App::$cmd; + + // + // Parse arguments + // + + $can_comment = perm_is_allowed(\App::$profile['profile_uid'],get_observer_hash(),'post_comments'); + + if(argc() > 3) { + $datatype = argv(2); + $datum = argv(3); + } else { + if(argc() > 2) { + $datatype = argv(2); + $datum = ''; + } + else + $datatype = 'summary'; + } + + if(argc() > 4) + $cmd = argv(4); + else + $cmd = 'view'; + + // + // Setup permissions structures + // + + $can_post = false; + $visitor = 0; + + + $owner_uid = \App::$data['channel']['channel_id']; + $owner_aid = \App::$data['channel']['channel_account_id']; + + $observer = \App::get_observer(); + + $can_post = perm_is_allowed($owner_uid,$observer['xchan_hash'],'write_storage'); + $can_view = perm_is_allowed($owner_uid,$observer['xchan_hash'],'view_storage'); + + if(! $can_view) { + notice( t('Access to this item is restricted.') . EOL); + return; + } + + $sql_extra = permissions_sql($owner_uid); + + $o = ""; + + $o .= "\r\n"; + + // tabs + + $_is_owner = (local_channel() && (local_channel() == $owner_uid)); + $o .= profile_tabs($a,$_is_owner, \App::$data['channel']['channel_address']); + + /** + * Display upload form + */ + + if( $can_post) { + + $uploader = ''; + + $ret = array('post_url' => z_root() . '/photos/' . \App::$data['channel']['channel_address'], + 'addon_text' => $uploader, + 'default_upload' => true); + + call_hooks('photo_upload_form',$ret); + + /* Show space usage */ + + $r = q("select sum(filesize) as total from photo where aid = %d and imgscale = 0 ", + intval(\App::$data['channel']['channel_account_id']) + ); + + + $limit = engr_units_to_bytes(service_class_fetch(\App::$data['channel']['channel_id'],'photo_upload_limit')); + if($limit !== false) { + $usage_message = sprintf( t("%1$.2f MB of %2$.2f MB photo storage used."), $r[0]['total'] / 1024000, $limit / 1024000 ); + } + else { + $usage_message = sprintf( t('%1$.2f MB photo storage used.'), $r[0]['total'] / 1024000 ); + } + + if($_is_owner) { + $channel = \App::get_channel(); + + $acl = new \Zotlabs\Access\AccessList($channel); + $channel_acl = $acl->get(); + + $lockstate = (($acl->is_private()) ? 'lock' : 'unlock'); + } + + $aclselect = (($_is_owner) ? populate_acl($channel_acl,false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_storage')) : ''); + + // this is wrong but is to work around an issue with js_upload wherein it chokes if these variables + // don't exist. They really should be set to a parseable representation of the channel's default permissions + // which can be processed by getSelected() + + if(! $aclselect) { + $aclselect = ''; + } + + $selname = (($datum) ? hex2bin($datum) : ''); + + $albums = ((array_key_exists('albums', \App::$data)) ? \App::$data['albums'] : photos_albums_list(\App::$data['channel'],\App::$data['observer'])); + + if(! $selname) { + $def_album = get_pconfig(\App::$data['channel']['channel_id'],'system','photo_path'); + if($def_album) { + $selname = filepath_macro($def_album); + $albums['album'][] = array('text' => $selname); + } + } + + $tpl = get_markup_template('photos_upload.tpl'); + $upload_form = replace_macros($tpl,array( + '$pagename' => t('Upload Photos'), + '$sessid' => session_id(), + '$usage' => $usage_message, + '$nickname' => \App::$data['channel']['channel_address'], + '$newalbum_label' => t('Enter an album name'), + '$newalbum_placeholder' => t('or select an existing album (doubleclick)'), + '$visible' => array('visible', t('Create a status post for this upload'), 0,'', array(t('No'), t('Yes')), 'onclick="showHideBodyTextarea();"'), + '$caption' => array('description', t('Caption (optional):')), + '$body' => array('body', t('Description (optional):'),'', 'Description will only appear in the status post'), + '$albums' => $albums['albums'], + '$selname' => $selname, + '$permissions' => t('Permissions'), + '$aclselect' => $aclselect, + '$lockstate' => $lockstate, + '$uploader' => $ret['addon_text'], + '$default' => (($ret['default_upload']) ? true : false), + '$uploadurl' => $ret['post_url'], + '$submit' => t('Submit') + + )); + + } + + // + // dispatch request + // + + /* + * Display a single photo album + */ + + if($datatype === 'album') { + + if(strlen($datum)) { + if((strlen($datum) & 1) || (! ctype_xdigit($datum))) { + notice( t('Album name could not be decoded') . EOL); + logger('mod_photos: illegal album encoding: ' . $datum); + $datum = ''; + } + } + + $album = (($datum) ? hex2bin($datum) : ''); + + + \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + + + $r = q("SELECT `resource_id`, max(`imgscale`) AS `imgscale` FROM `photo` WHERE `uid` = %d AND `album` = '%s' + AND `imgscale` <= 4 and photo_usage IN ( %d, %d ) and is_nsfw = %d $sql_extra GROUP BY `resource_id`", + intval($owner_uid), + dbesc($album), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + intval($unsafe) + ); + if(count($r)) { + \App::set_pager_total(count($r)); + \App::set_pager_itemspage(60); + } else { + goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address']); + } + + if($_GET['order'] === 'posted') + $order = 'ASC'; + else + $order = 'DESC'; + + + $r = q("SELECT p.resource_id, p.id, p.filename, p.mimetype, p.imgscale, p.description, p.created FROM photo p INNER JOIN + (SELECT resource_id, max(imgscale) imgscale FROM photo WHERE uid = %d AND album = '%s' AND imgscale <= 4 AND photo_usage IN ( %d, %d ) and is_nsfw = %d $sql_extra GROUP BY resource_id) ph + ON (p.resource_id = ph.resource_id AND p.imgscale = ph.imgscale) + ORDER BY created $order LIMIT %d OFFSET %d", + intval($owner_uid), + dbesc($album), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + intval($unsafe), + intval(\App::$pager['itemspage']), + intval(\App::$pager['start']) + ); + + //edit album name + $album_edit = null; + if(($album !== t('Profile Photos')) && ($album !== 'Profile Photos') && ($album !== 'Contact Photos') && ($album !== t('Contact Photos'))) { + if($can_post) { + $album_e = $album; + $albums = ((array_key_exists('albums', \App::$data)) ? \App::$data['albums'] : photos_albums_list(\App::$data['channel'],\App::$data['observer'])); + + // @fixme - syncronise actions with DAV + + // $edit_tpl = get_markup_template('album_edit.tpl'); + // $album_edit = replace_macros($edit_tpl,array( + // '$nametext' => t('Enter a new album name'), + // '$name_placeholder' => t('or select an existing one (doubleclick)'), + // '$nickname' => \App::$data['channel']['channel_address'], + // '$album' => $album_e, + // '$albums' => $albums['albums'], + // '$hexalbum' => bin2hex($album), + // '$submit' => t('Submit'), + // '$dropsubmit' => t('Delete Album') + // )); + + } + } + + if($_GET['order'] === 'posted') + $order = array(t('Show Newest First'), z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . bin2hex($album)); + else + $order = array(t('Show Oldest First'), z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . bin2hex($album) . '?f=&order=posted'); + + $photos = array(); + if(count($r)) { + $twist = 'rotright'; + foreach($r as $rr) { + + if($twist == 'rotright') + $twist = 'rotleft'; + else + $twist = 'rotright'; + + $ext = $phototypes[$rr['mimetype']]; + + $imgalt_e = $rr['filename']; + $desc_e = $rr['description']; + + $imagelink = (z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/image/' . $rr['resource_id'] + . (($_GET['order'] === 'posted') ? '?f=&order=posted' : '')); + + $photos[] = array( + 'id' => $rr['id'], + 'twist' => ' ' . $twist . rand(2,4), + 'link' => $imagelink, + 'title' => t('View Photo'), + 'src' => z_root() . '/photo/' . $rr['resource_id'] . '-' . $rr['imgscale'] . '.' .$ext, + 'alt' => $imgalt_e, + 'desc'=> $desc_e, + 'ext' => $ext, + 'hash'=> $rr['resource_id'], + 'unknown' => t('Unknown') + ); + } + } + + if($_REQUEST['aj']) { + if($photos) { + $o = replace_macros(get_markup_template('photosajax.tpl'),array( + '$photos' => $photos, + '$album_id' => bin2hex($album) + )); + } + else { + $o = '
    '; + } + echo $o; + killme(); + } + else { + $o .= ""; + $tpl = get_markup_template('photo_album.tpl'); + $o .= replace_macros($tpl, array( + '$photos' => $photos, + '$album' => $album, + '$album_id' => bin2hex($album), + '$album_edit' => array(t('Edit Album'), $album_edit), + '$can_post' => $can_post, + '$upload' => array(t('Upload'), z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/upload/' . bin2hex($album)), + '$order' => $order, + '$upload_form' => $upload_form, + '$usage' => $usage_message + )); + + } + + if((! $photos) && ($_REQUEST['aj'])) { + $o .= '
    '; + echo $o; + killme(); + } + + // $o .= paginate($a); + + return $o; + + } + + /** + * Display one photo + */ + + if($datatype === 'image') { + + \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + + // fetch image, item containing image, then comments + + $ph = q("SELECT id,aid,uid,xchan,resource_id,created,edited,title,`description`,album,filename,mimetype,height,width,filesize,imgscale,photo_usage,is_nsfw,allow_cid,allow_gid,deny_cid,deny_gid FROM `photo` WHERE `uid` = %d AND `resource_id` = '%s' + $sql_extra ORDER BY `imgscale` ASC ", + intval($owner_uid), + dbesc($datum) + ); + + if(! $ph) { + + /* Check again - this time without specifying permissions */ + + $ph = q("SELECT id FROM photo WHERE uid = %d AND resource_id = '%s' LIMIT 1", + intval($owner_uid), + dbesc($datum) + ); + if($ph) + notice( t('Permission denied. Access to this item may be restricted.') . EOL); + else + notice( t('Photo not available') . EOL ); + return; + } + + + + $prevlink = ''; + $nextlink = ''; + + if($_GET['order'] === 'posted') + $order = 'ASC'; + else + $order = 'DESC'; + + + $prvnxt = q("SELECT `resource_id` FROM `photo` WHERE `album` = '%s' AND `uid` = %d AND `imgscale` = 0 + $sql_extra ORDER BY `created` $order ", + dbesc($ph[0]['album']), + intval($owner_uid) + ); + + if(count($prvnxt)) { + for($z = 0; $z < count($prvnxt); $z++) { + if($prvnxt[$z]['resource_id'] == $ph[0]['resource_id']) { + $prv = $z - 1; + $nxt = $z + 1; + if($prv < 0) + $prv = count($prvnxt) - 1; + if($nxt >= count($prvnxt)) + $nxt = 0; + break; + } + } + + $prevlink = z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/image/' . $prvnxt[$prv]['resource_id'] . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''); + $nextlink = z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/image/' . $prvnxt[$nxt]['resource_id'] . (($_GET['order'] === 'posted') ? '?f=&order=posted' : ''); + } + + + if(count($ph) == 1) + $hires = $lores = $ph[0]; + if(count($ph) > 1) { + if($ph[1]['imgscale'] == 2) { + // original is 640 or less, we can display it directly + $hires = $lores = $ph[0]; + } + else { + $hires = $ph[0]; + $lores = $ph[1]; + } + } + + $album_link = z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . bin2hex($ph[0]['album']); + $tools = Null; + $lock = Null; + + if($can_post && ($ph[0]['uid'] == $owner_uid)) { + $tools = array( + 'profile'=>array(z_root() . '/profile_photo/use/'.$ph[0]['resource_id'], t('Use as profile photo')), + 'cover'=>array(z_root() . '/cover_photo/use/'.$ph[0]['resource_id'], t('Use as cover photo')), + ); + } + + // lockstate + $lockstate = ( ( (strlen($ph[0]['allow_cid']) || strlen($ph[0]['allow_gid']) + || strlen($ph[0]['deny_cid']) || strlen($ph[0]['deny_gid'])) ) + ? array('lock', t('Private Photo')) + : array('unlock', Null)); + + \App::$page['htmlhead'] .= ''; + + if($prevlink) + $prevlink = array($prevlink, t('Previous')); + + $photo = array( + 'href' => z_root() . '/photo/' . $hires['resource_id'] . '-' . $hires['imgscale'] . '.' . $phototypes[$hires['mimetype']], + 'title'=> t('View Full Size'), + 'src' => z_root() . '/photo/' . $lores['resource_id'] . '-' . $lores['imgscale'] . '.' . $phototypes[$lores['mimetype']] . '?f=&_u=' . datetime_convert('','','','ymdhis') + ); + + if($nextlink) + $nextlink = array($nextlink, t('Next')); + + + // Do we have an item for this photo? + + $linked_items = q("SELECT * FROM item WHERE resource_id = '%s' and resource_type = 'photo' + $sql_extra LIMIT 1", + dbesc($datum) + ); + + $map = null; + + if($linked_items) { + + xchan_query($linked_items); + $linked_items = fetch_post_tags($linked_items,true); + + $link_item = $linked_items[0]; + $item_normal = item_normal(); + + $r = q("select * from item where parent_mid = '%s' + $item_normal and uid = %d $sql_extra ", + dbesc($link_item['mid']), + intval($link_item['uid']) + + ); + + if($r) { + xchan_query($r); + $r = fetch_post_tags($r,true); + $r = conv_sort($r,'commented'); + } + + $tags = array(); + if($link_item['term']) { + $cnt = 0; + foreach($link_item['term'] as $t) { + $tags[$cnt] = array(0 => format_term_for_display($t)); + if($can_post && ($ph[0]['uid'] == $owner_uid)) { + $tags[$cnt][1] = 'tagrm/drop/' . $link_item['id'] . '/' . bin2hex($t['term']); //?f=&item=' . $link_item['id']; + $tags[$cnt][2] = t('Remove'); + } + $cnt ++; + } + } + + if((local_channel()) && (local_channel() == $link_item['uid'])) { + q("UPDATE `item` SET item_unseen = 0 WHERE parent = %d and uid = %d and item_unseen = 1", + intval($link_item['parent']), + intval(local_channel()) + ); + } + + if($link_item['coord']) { + $map = generate_map($link_item['coord']); + } + } + + // logger('mod_photo: link_item' . print_r($link_item,true)); + + // FIXME - remove this when we move to conversation module + + $r = $r[0]['children']; + + $edit = null; + if($can_post) { + $album_e = $ph[0]['album']; + $caption_e = $ph[0]['description']; + $aclselect_e = (($_is_owner) ? populate_acl($ph[0], true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_storage')) : ''); + $albums = ((array_key_exists('albums', \App::$data)) ? \App::$data['albums'] : photos_albums_list(\App::$data['channel'],\App::$data['observer'])); + + $_SESSION['album_return'] = bin2hex($ph[0]['album']); + + $edit = array( + 'edit' => t('Edit photo'), + 'id' => $link_item['id'], + 'rotatecw' => t('Rotate CW (right)'), + 'rotateccw' => t('Rotate CCW (left)'), + 'albums' => $albums['albums'], + 'album' => $album_e, + 'newalbum_label' => t('Enter a new album name'), + 'newalbum_placeholder' => t('or select an existing one (doubleclick)'), + 'nickname' => \App::$data['channel']['channel_address'], + 'resource_id' => $ph[0]['resource_id'], + 'capt_label' => t('Caption'), + 'caption' => $caption_e, + 'tag_label' => t('Add a Tag'), + 'permissions' => t('Permissions'), + 'aclselect' => $aclselect_e, + 'lockstate' => $lockstate[0], + 'help_tags' => t('Example: @bob, @Barbara_Jensen, @jim@example.com'), + 'item_id' => ((count($linked_items)) ? $link_item['id'] : 0), + 'adult_enabled' => feature_enabled($owner_uid,'adult_photo_flagging'), + 'adult' => array('adult',t('Flag as adult in album view'), intval($ph[0]['is_nsfw']),''), + 'submit' => t('Submit'), + 'delete' => t('Delete Photo') + ); + } + + if(count($linked_items)) { + + $cmnt_tpl = get_markup_template('comment_item.tpl'); + $tpl = get_markup_template('photo_item.tpl'); + $return_url = \App::$cmd; + + $like_tpl = get_markup_template('like_noshare.tpl'); + + $likebuttons = ''; + + if($can_post || $can_comment) { + $likebuttons = array( + 'id' => $link_item['id'], + 'likethis' => t("I like this \x28toggle\x29"), + 'nolike' => t("I don't like this \x28toggle\x29"), + 'share' => t('Share'), + 'wait' => t('Please wait') + ); + } + + $comments = ''; + if(! count($r)) { + if($can_post || $can_comment) { + $commentbox = replace_macros($cmnt_tpl,array( + '$return_path' => '', + '$mode' => 'photos', + '$jsreload' => $return_url, + '$type' => 'wall-comment', + '$id' => $link_item['id'], + '$parent' => $link_item['id'], + '$profile_uid' => $owner_uid, + '$mylink' => $observer['xchan_url'], + '$mytitle' => t('This is you'), + '$myphoto' => $observer['xchan_photo_s'], + '$comment' => t('Comment'), + '$submit' => t('Submit'), + '$preview' => t('Preview'), + '$ww' => '', + '$feature_encrypt' => false + )); + } + } + + $alike = array(); + $dlike = array(); + + $like = ''; + $dislike = ''; + + $conv_responses = array( + 'like' => array('title' => t('Likes','title')),'dislike' => array('title' => t('Dislikes','title')), + 'agree' => array('title' => t('Agree','title')),'disagree' => array('title' => t('Disagree','title')), 'abstain' => array('title' => t('Abstain','title')), + 'attendyes' => array('title' => t('Attending','title')), 'attendno' => array('title' => t('Not attending','title')), 'attendmaybe' => array('title' => t('Might attend','title')) + ); + + + + + if($r) { + + foreach($r as $item) { + builtin_activity_puller($item, $conv_responses); + } + + + $like_count = ((x($alike,$link_item['mid'])) ? $alike[$link_item['mid']] : ''); + $like_list = ((x($alike,$link_item['mid'])) ? $alike[$link_item['mid'] . '-l'] : ''); + if (count($like_list) > MAX_LIKERS) { + $like_list_part = array_slice($like_list, 0, MAX_LIKERS); + array_push($like_list_part, '' . t('View all') . ''); + } else { + $like_list_part = ''; + } + $like_button_label = tt('Like','Likes',$like_count,'noun'); + + //if (feature_enabled($conv->get_profile_owner(),'dislike')) { + $dislike_count = ((x($dlike,$link_item['mid'])) ? $dlike[$link_item['mid']] : ''); + $dislike_list = ((x($dlike,$link_item['mid'])) ? $dlike[$link_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, '' . t('View all') . ''); + } else { + $dislike_list_part = ''; + } + //} + + + $like = ((isset($alike[$link_item['mid']])) ? format_like($alike[$link_item['mid']],$alike[$link_item['mid'] . '-l'],'like',$link_item['mid']) : ''); + $dislike = ((isset($dlike[$link_item['mid']])) ? format_like($dlike[$link_item['mid']],$dlike[$link_item['mid'] . '-l'],'dislike',$link_item['mid']) : ''); + + // display comments + + foreach($r as $item) { + $comment = ''; + $template = $tpl; + $sparkle = ''; + + if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) + continue; + + $redirect_url = z_root() . '/redir/' . $item['cid'] ; + + + $profile_url = zid($item['author']['xchan_url']); + $sparkle = ''; + + + $profile_name = $item['author']['xchan_name']; + $profile_avatar = $item['author']['xchan_photo_m']; + + $profile_link = $profile_url; + + $drop = ''; + + if($observer['xchan_hash'] === $item['author_xchan'] || $observer['xchan_hash'] === $item['owner_xchan']) + $drop = replace_macros(get_markup_template('photo_drop.tpl'), array('$id' => $item['id'], '$delete' => t('Delete'))); + + + $name_e = $profile_name; + $title_e = $item['title']; + unobscure($item); + $body_e = prepare_text($item['body'],$item['mimetype']); + + $comments .= replace_macros($template,array( + '$id' => $item['id'], + '$mode' => 'photos', + '$profile_url' => $profile_link, + '$name' => $name_e, + '$thumb' => $profile_avatar, + '$sparkle' => $sparkle, + '$title' => $title_e, + '$body' => $body_e, + '$ago' => relative_date($item['created']), + '$indent' => (($item['parent'] != $item['id']) ? ' comment' : ''), + '$drop' => $drop, + '$comment' => $comment + )); + + } + + if($can_post || $can_comment) { + $commentbox = replace_macros($cmnt_tpl,array( + '$return_path' => '', + '$jsreload' => $return_url, + '$type' => 'wall-comment', + '$id' => $link_item['id'], + '$parent' => $link_item['id'], + '$profile_uid' => $owner_uid, + '$mylink' => $observer['xchan_url'], + '$mytitle' => t('This is you'), + '$myphoto' => $observer['xchan_photo_s'], + '$comment' => t('Comment'), + '$submit' => t('Submit'), + '$ww' => '' + )); + } + + } + $paginate = paginate($a); + } + + $album_e = array($album_link,$ph[0]['album']); + $like_e = $like; + $dislike_e = $dislike; + + + $response_verbs = array('like'); + if(feature_enabled($owner_uid,'dislike')) + $response_verbs[] = 'dislike'; + + + $responses = get_responses($conv_responses,$response_verbs,'',$link_item); + + $photo_tpl = get_markup_template('photo_view.tpl'); + $o .= replace_macros($photo_tpl, array( + '$id' => $ph[0]['id'], + '$album' => $album_e, + '$tools_label' => t('Photo Tools'), + '$tools' => $tools, + '$lock' => $lockstate[1], + '$photo' => $photo, + '$prevlink' => $prevlink, + '$nextlink' => $nextlink, + '$desc' => $ph[0]['description'], + '$filename' => $ph[0]['filename'], + '$unknown' => t('Unknown'), + '$tag_hdr' => t('In This Photo:'), + '$tags' => $tags, + 'responses' => $responses, + '$edit' => $edit, + '$map' => $map, + '$map_text' => t('Map'), + '$likebuttons' => $likebuttons, + '$like' => $like_e, + '$dislike' => $dislike_e, + '$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' => $dislike_count, //((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_count : ''), + '$dislike_list' => $dislike_list, //((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list : ''), + '$dislike_list_part' => $dislike_list_part, //((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list_part : ''), + '$dislike_button_label' => $dislike_button_label, //((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_button_label : ''), + '$modal_dismiss' => t('Close'), + '$comments' => $comments, + '$commentbox' => $commentbox, + '$paginate' => $paginate, + )); + + \App::$data['photo_html'] = $o; + + return $o; + } + + // Default - show recent photos with upload link (if applicable) + //$o = ''; + + \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + + + $r = q("SELECT `resource_id`, max(`imgscale`) AS `imgscale` FROM `photo` WHERE `uid` = %d + and photo_usage in ( %d, %d ) and is_nsfw = %d $sql_extra GROUP BY `resource_id`", + intval(\App::$data['channel']['channel_id']), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + intval($unsafe) + ); + if($r) { + \App::set_pager_total(count($r)); + \App::set_pager_itemspage(60); + } + + $r = q("SELECT p.resource_id, p.id, p.filename, p.mimetype, p.album, p.imgscale, p.created FROM photo p + INNER JOIN ( SELECT resource_id, max(imgscale) imgscale FROM photo + WHERE uid = %d AND photo_usage IN ( %d, %d ) + AND is_nsfw = %d $sql_extra group by resource_id ) ph + ON (p.resource_id = ph.resource_id and p.imgscale = ph.imgscale) + ORDER by p.created DESC LIMIT %d OFFSET %d", + intval(\App::$data['channel']['channel_id']), + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + intval($unsafe), + intval(\App::$pager['itemspage']), + intval(\App::$pager['start']) + ); + + + + $photos = array(); + if($r) { + $twist = 'rotright'; + foreach($r as $rr) { + if($twist == 'rotright') + $twist = 'rotleft'; + else + $twist = 'rotright'; + $ext = $phototypes[$rr['mimetype']]; + + if(\App::get_template_engine() === 'internal') { + $alt_e = template_escape($rr['filename']); + $name_e = template_escape($rr['album']); + } + else { + $alt_e = $rr['filename']; + $name_e = $rr['album']; + } + + $photos[] = array( + 'id' => $rr['id'], + 'twist' => ' ' . $twist . rand(2,4), + 'link' => z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/image/' . $rr['resource_id'], + 'title' => t('View Photo'), + 'src' => z_root() . '/photo/' . $rr['resource_id'] . '-' . ((($rr['imgscale']) == 6) ? 4 : $rr['imgscale']) . '.' . $ext, + 'alt' => $alt_e, + 'album' => array( + 'link' => z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . bin2hex($rr['album']), + 'name' => $name_e, + 'alt' => t('View Album'), + ), + + ); + } + } + + if($_REQUEST['aj']) { + if($photos) { + $o = replace_macros(get_markup_template('photosajax.tpl'),array( + '$photos' => $photos, + '$album_id' => bin2hex(t('Recent Photos')) + )); + } + else { + $o = '
    '; + } + echo $o; + killme(); + } + else { + $o .= ""; + $tpl = get_markup_template('photos_recent.tpl'); + $o .= replace_macros($tpl, array( + '$title' => t('Recent Photos'), + '$album_id' => bin2hex(t('Recent Photos')), + '$can_post' => $can_post, + '$upload' => array(t('Upload'), z_root().'/photos/'.\App::$data['channel']['channel_address'].'/upload'), + '$photos' => $photos, + '$upload_form' => $upload_form, + '$usage' => $usage_message + )); + + } + + if((! $photos) && ($_REQUEST['aj'])) { + $o .= '
    '; + echo $o; + killme(); + } + + // paginate($a); + return $o; + } + + +} diff --git a/Zotlabs/Module/Ping.php b/Zotlabs/Module/Ping.php new file mode 100644 index 000000000..5cbf45daa --- /dev/null +++ b/Zotlabs/Module/Ping.php @@ -0,0 +1,498 @@ + $m); + } + unset($_SESSION['sysmsg']); + } + if(x($_SESSION, 'sysmsg_info')){ + foreach ($_SESSION['sysmsg_info'] as $m){ + $result['info'][] = array('message' => $m); + } + unset($_SESSION['sysmsg_info']); + } + if(! ($vnotify & VNOTIFY_INFO)) + $result['info'] = array(); + if(! ($vnotify & VNOTIFY_ALERT)) + $result['notice'] = array(); + + + if(\App::$install) { + echo json_encode($result); + killme(); + } + + /** + * Update chat presence indication (if applicable) + */ + + if(get_observer_hash() && (! $result['invalid'])) { + $r = q("select cp_id, cp_room from chatpresence where cp_xchan = '%s' and cp_client = '%s' and cp_room = 0 limit 1", + dbesc(get_observer_hash()), + dbesc($_SERVER['REMOTE_ADDR']) + ); + $basic_presence = false; + if($r) { + $basic_presence = true; + q("update chatpresence set cp_last = '%s' where cp_id = %d", + dbesc(datetime_convert()), + intval($r[0]['cp_id']) + ); + } + if(! $basic_presence) { + q("insert into chatpresence ( cp_xchan, cp_last, cp_status, cp_client) + values( '%s', '%s', '%s', '%s' ) ", + dbesc(get_observer_hash()), + dbesc(datetime_convert()), + dbesc('online'), + dbesc($_SERVER['REMOTE_ADDR']) + ); + } + } + + /** + * Chatpresence continued... if somebody hasn't pinged recently, they've most likely left the page + * and shouldn't count as online anymore. We allow an expection for bots. + */ + + q("delete from chatpresence where cp_last < %s - INTERVAL %s and cp_client != 'auto' ", + db_utcnow(), db_quoteinterval('3 MINUTE') + ); + + if((! local_channel()) || ($result['invalid'])) { + echo json_encode($result); + killme(); + } + + /** + * Everything following is only permitted under the context of a locally authenticated site member. + */ + + + /** + * Handle "mark all xyz notifications read" requests. + */ + + // mark all items read + if(x($_REQUEST, 'markRead') && local_channel()) { + switch($_REQUEST['markRead']) { + case 'network': + $r = q("update item set item_unseen = 0 where item_unseen = 1 and uid = %d", + intval(local_channel()) + ); + break; + case 'home': + $r = q("update item set item_unseen = 0 where item_unseen = 1 and item_wall = 1 and uid = %d", + intval(local_channel()) + ); + break; + case 'messages': + $r = q("update mail set mail_seen = 1 where mail_seen = 0 and channel_id = %d ", + intval(local_channel()) + ); + break; + case 'all_events': + $r = q("update event set `dimissed` = 1 where `dismissed` = 0 and uid = %d AND dtstart < '%s' AND dtstart > '%s' ", + intval(local_channel()), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days')) + ); + break; + case 'notify': + $r = q("update notify set seen = 1 where uid = %d", + intval(local_channel()) + ); + break; + default: + break; + } + } + + if(x($_REQUEST, 'markItemRead') && local_channel()) { + $r = q("update item set item_unseen = 0 where parent = %d and uid = %d", + intval($_REQUEST['markItemRead']), + intval(local_channel()) + ); + } + + + + /** + * URL ping/something will return detail for "something", e.g. a json list with which to populate a notification + * dropdown menu. + */ + + if(argc() > 1 && argv(1) === 'notify') { + $t = q("select count(*) as total from notify where uid = %d and seen = 0", + intval(local_channel()) + ); + if($t && intval($t[0]['total']) > 49) { + $z = q("select * from notify where uid = %d + and seen = 0 order by created desc limit 50", + intval(local_channel()) + ); + } + else { + $z1 = q("select * from notify where uid = %d + and seen = 0 order by created desc limit 50", + intval(local_channel()) + ); + $z2 = q("select * from notify where uid = %d + and seen = 1 order by created desc limit %d", + intval(local_channel()), + intval(50 - intval($t[0]['total'])) + ); + $z = array_merge($z1,$z2); + } + + if(count($z)) { + foreach($z as $zz) { + $notifs[] = array( + 'notify_link' => z_root() . '/notify/view/' . $zz['id'], + 'name' => $zz['xname'], + 'url' => $zz['url'], + 'photo' => $zz['photo'], + 'when' => relative_date($zz['created']), + 'hclass' => (($zz['seen']) ? 'notify-seen' : 'notify-unseen'), + 'message' => strip_tags(bbcode($zz['msg'])) + ); + } + } + + echo json_encode(array('notify' => $notifs)); + killme(); + } + + if(argc() > 1 && argv(1) === 'messages') { + $channel = \App::get_channel(); + $t = q("select mail.*, xchan.* from mail left join xchan on xchan_hash = from_xchan + where channel_id = %d and mail_seen = 0 and mail_deleted = 0 + and from_xchan != '%s' order by created desc limit 50", + intval(local_channel()), + dbesc($channel['channel_hash']) + ); + + if($t) { + foreach($t as $zz) { + $notifs[] = array( + 'notify_link' => z_root() . '/mail/' . $zz['id'], + 'name' => $zz['xchan_name'], + 'url' => $zz['xchan_url'], + 'photo' => $zz['xchan_photo_s'], + 'when' => relative_date($zz['created']), + 'hclass' => (intval($zz['mail_seen']) ? 'notify-seen' : 'notify-unseen'), + 'message' => t('sent you a private message'), + ); + } + } + + echo json_encode(array('notify' => $notifs)); + killme(); + } + + if(argc() > 1 && (argv(1) === 'network' || argv(1) === 'home')) { + $result = array(); + + $r = q("SELECT * FROM item + WHERE item_unseen = 1 and uid = %d $item_normal + and author_xchan != '%s' ORDER BY created DESC limit 300", + intval(local_channel()), + dbesc($ob_hash) + ); + + if($r) { + xchan_query($r); + foreach($r as $item) { + if((argv(1) === 'home') && (! intval($item['item_wall']))) + continue; + $result[] = \Zotlabs\Lib\Enotify::format($item); + } + } + // logger('ping (network||home): ' . print_r($result, true), LOGGER_DATA); + echo json_encode(array('notify' => $result)); + killme(); + } + + if(argc() > 1 && (argv(1) === 'intros')) { + $result = array(); + + $r = q("SELECT * FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ORDER BY abook_created DESC LIMIT 50", + intval(local_channel()) + ); + + if($r) { + foreach($r as $rr) { + $result[] = array( + 'notify_link' => z_root() . '/connections/ifpending', + 'name' => $rr['xchan_name'], + 'url' => $rr['xchan_url'], + 'photo' => $rr['xchan_photo_s'], + 'when' => relative_date($rr['abook_created']), + 'hclass' => ('notify-unseen'), + 'message' => t('added your channel') + ); + } + } + logger('ping (intros): ' . print_r($result, true), LOGGER_DATA); + echo json_encode(array('notify' => $result)); + killme(); + } + + if(argc() > 1 && (argv(1) === 'all_events')) { + $bd_format = t('g A l F d') ; // 8 AM Friday January 18 + + $result = array(); + + $r = q("SELECT * FROM event left join xchan on event_xchan = xchan_hash + WHERE `event`.`uid` = %d AND dtstart < '%s' AND dtstart > '%s' and `dismissed` = 0 + and etype in ( 'event', 'birthday' ) + ORDER BY `dtstart` DESC LIMIT 1000", + intval(local_channel()), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days')) + ); + + if($r) { + foreach($r as $rr) { + if($rr['adjust']) + $md = datetime_convert('UTC', date_default_timezone_get(), $rr['dtstart'], 'Y/m'); + else + $md = datetime_convert('UTC', 'UTC', $rr['dtstart'], 'Y/m'); + + $strt = datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart']); + $today = ((substr($strt, 0, 10) === datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d')) ? true : false); + + $when = day_translate(datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart'], $bd_format)) . (($today) ? ' ' . t('[today]') : ''); + + $result[] = array( + 'notify_link' => z_root() . '/events', // FIXME this takes you to an edit page and it may not be yours, we really want to just view the single event --> '/events/event/' . $rr['event_hash'], + 'name' => $rr['xchan_name'], + 'url' => $rr['xchan_url'], + 'photo' => $rr['xchan_photo_s'], + 'when' => $when, + 'hclass' => ('notify-unseen'), + 'message' => t('posted an event') + ); + } + } + logger('ping (all_events): ' . print_r($result, true), LOGGER_DATA); + echo json_encode(array('notify' => $result)); + killme(); + } + + + + /** + * Normal ping - just the counts, no detail + */ + + if($vnotify & VNOTIFY_SYSTEM) { + $t = q("select count(*) as total from notify where uid = %d and seen = 0", + intval(local_channel()) + ); + if($t) + $result['notify'] = intval($t[0]['total']); + } + + $t1 = dba_timer(); + + if($vnotify & (VNOTIFY_NETWORK|VNOTIFY_CHANNEL)) { + $r = q("SELECT id, item_wall FROM item + WHERE item_unseen = 1 and uid = %d + $item_normal + and author_xchan != '%s'", + intval(local_channel()), + dbesc($ob_hash) + ); + + if($r) { + $arr = array('items' => $r); + call_hooks('network_ping', $arr); + + foreach ($r as $it) { + if(intval($it['item_wall'])) + $result['home'] ++; + else + $result['network'] ++; + } + } + } + if(! ($vnotify & VNOTIFY_NETWORK)) + $result['network'] = 0; + if(! ($vnotify & VNOTIFY_CHANNEL)) + $result['home'] = 0; + + + $t2 = dba_timer(); + + if($vnotify & VNOTIFY_INTRO) { + $intr = q("SELECT COUNT(abook.abook_id) AS total FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ", + intval(local_channel()) + ); + + $t3 = dba_timer(); + + if($intr) + $result['intros'] = intval($intr[0]['total']); + } + + $t4 = dba_timer(); + $channel = \App::get_channel(); + + if($vnotify & VNOTIFY_MAIL) { + $mails = q("SELECT count(id) as total from mail + WHERE channel_id = %d AND mail_seen = 0 and from_xchan != '%s' ", + intval(local_channel()), + dbesc($channel['channel_hash']) + ); + if($mails) + $result['mail'] = intval($mails[0]['total']); + } + + if($vnotify & VNOTIFY_REGISTER) { + if (\App::$config['system']['register_policy'] == REGISTER_APPROVE && is_site_admin()) { + $regs = q("SELECT count(account_id) as total from account where (account_flags & %d) > 0", + intval(ACCOUNT_PENDING) + ); + if($regs) + $result['register'] = intval($regs[0]['total']); + } + } + + $t5 = dba_timer(); + + if($vnotify & (VNOTIFY_EVENT|VNOTIFY_EVENTTODAY|VNOTIFY_BIRTHDAY)) { + $events = q("SELECT etype, dtstart, adjust FROM `event` + WHERE `event`.`uid` = %d AND dtstart < '%s' AND dtstart > '%s' and `dismissed` = 0 + and etype in ( 'event', 'birthday' ) + ORDER BY `dtstart` ASC ", + intval(local_channel()), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), + dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days')) + ); + + if($events) { + $result['all_events'] = count($events); + + if($result['all_events']) { + $str_now = datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d'); + foreach($events as $x) { + $bd = false; + if($x['etype'] === 'birthday') { + $result['birthdays'] ++; + $bd = true; + } + else { + $result['events'] ++; + } + if(datetime_convert('UTC', ((intval($x['adjust'])) ? date_default_timezone_get() : 'UTC'), $x['dtstart'], 'Y-m-d') === $str_now) { + $result['all_events_today'] ++; + if($bd) + $result['birthdays_today'] ++; + else + $result['events_today'] ++; + } + } + } + } + } + if(! ($vnotify & VNOTIFY_EVENT)) + $result['all_events'] = $result['events'] = 0; + if(! ($vnotify & VNOTIFY_EVENTTODAY)) + $result['all_events_today'] = $result['events_today'] = 0; + if(! ($vnotify & VNOTIFY_BIRTHDAY)) + $result['birthdays'] = 0; + + + $x = json_encode($result); + + $t6 = dba_timer(); + + // logger('ping timer: ' . sprintf('%01.4f %01.4f %01.4f %01.4f %01.4f %01.4f',$t6 - $t5, $t5 - $t4, $t4 - $t3, $t3 - $t2, $t2 - $t1, $t1 - $t0)); + + echo $x; + killme(); + } + +} diff --git a/Zotlabs/Module/Poco.php b/Zotlabs/Module/Poco.php new file mode 100644 index 000000000..85c9348c0 --- /dev/null +++ b/Zotlabs/Module/Poco.php @@ -0,0 +1,13 @@ +' : $channel['channel_allow_cid']); + $allow_gid = (($item_private) ? '' : $channel['channel_allow_gid']); + $deny_cid = (($item_private) ? '' : $channel['channel_deny_cid']); + $deny_gid = (($item_private) ? '' : $channel['channel_deny_gid']); + } + + + $arr = array(); + + $arr['item_wall'] = 1; + $arr['owner_xchan'] = (($parent_item) ? $parent_item['owner_xchan'] : $channel['channel_hash']); + $arr['parent_mid'] = (($parent_mid) ? $parent_mid : $mid); + $arr['title'] = ''; + $arr['allow_cid'] = $allow_cid; + $arr['allow_gid'] = $allow_gid; + $arr['deny_cid'] = $deny_cid; + $arr['deny_gid'] = $deny_gid; + $arr['verb'] = $activity; + $arr['item_private'] = $item_private; + $arr['obj_type'] = ACTIVITY_OBJ_PERSON; + $arr['body'] = '[zrl=' . $channel['xchan_url'] . ']' . $channel['xchan_name'] . '[/zrl]' . ' ' . t($verbs[$verb][0]) . ' ' . '[zrl=' . $target['xchan_url'] . ']' . $target['xchan_name'] . '[/zrl]'; + + $obj = array( + 'type' => ACTIVITY_OBJ_PERSON, + 'title' => $target['xchan_name'], + 'id' => $target['xchan_hash'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $target['xchan_url']), + array('rel' => 'photo', 'type' => $target['xchan_photo_mimetype'], 'href' => $target['xchan_photo_l']) + ), + ); + + $arr['obj'] = json_encode($obj); + + $arr['item_origin'] = 1; + $arr['item_wall'] = 1; + $arr['item_unseen'] = 1; + if(! $parent_item) + $item['item_thread_top'] = 1; + + + post_activity_item($arr); + + return; + } + + + + function get() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + $name = ''; + $id = ''; + + if(intval($_REQUEST['c'])) { + $r = q("select abook_id, xchan_name from abook left join xchan on abook_xchan = xchan_hash + where abook_id = %d and abook_channel = %d limit 1", + intval($_REQUEST['c']), + intval(local_channel()) + ); + if($r) { + $name = $r[0]['xchan_name']; + $id = $r[0]['abook_id']; + } + } + + $parent = ((x($_REQUEST,'parent')) ? intval($_REQUEST['parent']) : '0'); + + $verbs = get_poke_verbs(); + + $shortlist = array(); + foreach($verbs as $k => $v) + if($v[1] !== 'NOTRANSLATION') + $shortlist[] = array($k,$v[1]); + + + $poke_basic = get_config('system','poke_basic'); + if($poke_basic) { + $title = t('Poke'); + $desc = t('Poke somebody'); + } + else { + $title = t('Poke/Prod'); + $desc = t('Poke, prod or do other things to somebody'); + } + + $o = replace_macros(get_markup_template('poke_content.tpl'),array( + '$title' => $title, + '$poke_basic' => $poke_basic, + '$desc' => $desc, + '$clabel' => t('Recipient'), + '$choice' => t('Choose what you wish to do to recipient'), + '$verbs' => $shortlist, + '$parent' => $parent, + '$prv_desc' => t('Make this post private'), + '$private' => array('private', t('Make this post private'), false, ''), + '$submit' => t('Submit'), + '$name' => $name, + '$id' => $id + )); + + return $o; + + } +} diff --git a/Zotlabs/Module/Post.php b/Zotlabs/Module/Post.php new file mode 100644 index 000000000..af231ab50 --- /dev/null +++ b/Zotlabs/Module/Post.php @@ -0,0 +1,36 @@ + $r[0]['xlink_rating'],'rating_text' => $r[0]['xlink_rating_text'])); + killme(); + } + + function post() { + + if(! local_channel()) + return; + + $channel = \App::get_channel(); + + $target = trim($_REQUEST['target']); + if(! $target) + return; + + if($target === $channel['channel_hash']) + return; + + $rating = intval($_POST['rating']); + if($rating < (-10)) + $rating = (-10); + if($rating > 10) + $rating = 10; + + $rating_text = trim(escape_tags($_REQUEST['rating_text'])); + + $signed = $target . '.' . $rating . '.' . $rating_text; + + $sig = base64url_encode(rsa_sign($signed,$channel['channel_prvkey'])); + + + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc($target) + ); + if($z) { + $record = $z[0]['xlink_id']; + $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' + where xlink_id = %d", + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()), + intval($record) + ); + } + else { + $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", + dbesc($channel['channel_hash']), + dbesc($target), + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()) + ); + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc($orig_record[0]['abook_xchan']) + ); + if($z) + $record = $z[0]['xlink_id']; + } + if($record) { + \Zotlabs\Daemon\Master::Summon(array('Ratenotif','rating',$record)); + } + + json_return_and_die(array('result' => true));; + } + + + + + + + + + + + + +} diff --git a/Zotlabs/Module/Pretheme.php b/Zotlabs/Module/Pretheme.php new file mode 100644 index 000000000..120fd5359 --- /dev/null +++ b/Zotlabs/Module/Pretheme.php @@ -0,0 +1,28 @@ + get_theme_screenshot($theme), 'desc' => $desc, 'version' => $version, 'credits' => $credits)); + } + killme(); + } + +} diff --git a/Zotlabs/Module/Probe.php b/Zotlabs/Module/Probe.php new file mode 100644 index 000000000..7fc0e8ff5 --- /dev/null +++ b/Zotlabs/Module/Probe.php @@ -0,0 +1,45 @@ +Probe Diagnostic'; + + $o .= '
    '; + $o .= 'Lookup address: '; + $o .= '
    '; + + $o .= '

    '; + + if(x($_GET,'addr')) { + $channel = \App::get_channel(); + $addr = trim($_GET['addr']); + $do_import = ((intval($_GET['import']) && is_site_admin()) ? true : false); + + $j = \Zotlabs\Zot\Finger::run($addr,$channel,false); + + $o .= '
    ';
    +			if(! $j['success']) {
    +				$o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n");
    +				$o .= "https connection failed. Trying again with auto failover to http.\r\n\r\n";
    +				$j = \Zotlabs\Zot\Finger::run($addr,$channel,true);
    +				if(! $j['success']) 
    +					$o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n");
    +	
    +			}
    +			if($do_import && $j)
    +				$x = import_xchan($j);
    +			if($j && $j['permissions'] && $j['permissions']['iv'])
    +				$j['permissions'] = json_decode(crypto_unencapsulate($j['permissions'],$channel['channel_prvkey']),true);
    +			$o .= str_replace("\n",'
    ',print_r($j,true)); + $o .= '
    '; + } + return $o; + } + +} diff --git a/Zotlabs/Module/Profile.php b/Zotlabs/Module/Profile.php new file mode 100644 index 000000000..9e868db92 --- /dev/null +++ b/Zotlabs/Module/Profile.php @@ -0,0 +1,90 @@ + 1) + $which = argv(1); + else { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $profile = ''; + $channel = \App::get_channel(); + + if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) { + $which = $channel['channel_address']; + $profile = argv(1); + $r = q("select profile_guid from profile where id = %d and uid = %d limit 1", + intval($profile), + intval(local_channel()) + ); + if(! $r) + $profile = ''; + $profile = $r[0]['profile_guid']; + } + + \App::$page['htmlhead'] .= '' . "\r\n" ; + + if(! $profile) { + $x = q("select channel_id as profile_uid from channel where channel_address = '%s' limit 1", + dbesc(argv(1)) + ); + if($x) { + \App::$profile = $x[0]; + } + } + + profile_load($which,$profile); + + + } + + function get() { + + if(observer_prohibited(true)) { + return login(); + } + + $groups = array(); + + $tab = 'profile'; + $o = ''; + + if(! (perm_is_allowed(\App::$profile['profile_uid'],get_observer_hash(), 'view_profile'))) { + notice( t('Permission denied.') . EOL); + return; + } + + + $is_owner = ((local_channel()) && (local_channel() == \App::$profile['profile_uid']) ? true : false); + + if(\App::$profile['hidewall'] && (! $is_owner) && (! remote_channel())) { + notice( t('Permission denied.') . EOL); + return; + } + + $o .= profile_tabs($a, $is_owner, \App::$profile['channel_address']); + + \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + + $o .= advanced_profile($a); + call_hooks('profile_advanced',$o); + return $o; + + } + +} diff --git a/Zotlabs/Module/Profile_photo.php b/Zotlabs/Module/Profile_photo.php new file mode 100644 index 000000000..f459f7deb --- /dev/null +++ b/Zotlabs/Module/Profile_photo.php @@ -0,0 +1,461 @@ +is_valid()) { + + $im->cropImage(300,$srcX,$srcY,$srcW,$srcH); + + $aid = get_account_id(); + + $p = [ + 'aid' => $aid, + 'uid' => local_channel(), + 'resource_id' => $base_image['resource_id'], + 'filename' => $base_image['filename'], + 'album' => t('Profile Photos') + ]; + + $p['imgscale'] = PHOTO_RES_PROFILE_300; + $p['photo_usage'] = (($is_default_profile) ? PHOTO_PROFILE : PHOTO_NORMAL); + + $r1 = $im->save($p); + + $im->scaleImage(80); + $p['imgscale'] = PHOTO_RES_PROFILE_80; + + $r2 = $im->save($p); + + $im->scaleImage(48); + $p['imgscale'] = PHOTO_RES_PROFILE_48; + + $r3 = $im->save($p); + + if($r1 === false || $r2 === false || $r3 === false) { + // if one failed, delete them all so we can start over. + notice( t('Image resize failed.') . EOL ); + $x = q("delete from photo where resource_id = '%s' and uid = %d and imgscale in ( %d, %d, %d ) ", + dbesc($base_image['resource_id']), + local_channel(), + intval(PHOTO_RES_PROFILE_300), + intval(PHOTO_RES_PROFILE_80), + intval(PHOTO_RES_PROFILE_48) + ); + return; + } + + $channel = \App::get_channel(); + + // If setting for the default profile, unset the profile photo flag from any other photos I own + + if($is_default_profile) { + $r = q("UPDATE photo SET photo_usage = %d WHERE photo_usage = %d + AND resource_id != '%s' AND `uid` = %d", + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + dbesc($base_image['resource_id']), + intval(local_channel()) + ); + + send_profile_photo_activity($channel,$base_image,$profile); + + } + else { + $r = q("update profile set photo = '%s', thumb = '%s' where id = %d and uid = %d", + dbesc(z_root() . '/photo/' . $base_image['resource_id'] . '-4'), + dbesc(z_root() . '/photo/' . $base_image['resource_id'] . '-5'), + intval($_REQUEST['profile']), + intval(local_channel()) + ); + } + + profiles_build_sync(local_channel()); + + // We'll set the updated profile-photo timestamp even if it isn't the default profile, + // so that browsers will do a cache update unconditionally + + + $r = q("UPDATE xchan set xchan_photo_mimetype = '%s', xchan_photo_date = '%s' + where xchan_hash = '%s'", + dbesc($im->getType()), + dbesc(datetime_convert()), + dbesc($channel['xchan_hash']) + ); + // Similarly, tell the nav bar to bypass the cache and update the avater image. + $_SESSION['reload_avatar'] = true; + + info( t('Shift-reload the page or clear browser cache if the new photo does not display immediately.') . EOL); + + // Update directory in background + \Zotlabs\Daemon\Master::Summon(array('Directory',$channel['channel_id'])); + + // Now copy profile-permissions to pictures, to prevent privacyleaks by automatically created folder 'Profile Pictures' + + profile_photo_set_profile_perms(local_channel(),$_REQUEST['profile']); + } + else + notice( t('Unable to process image') . EOL); + } + + goaway(z_root() . '/profiles'); + return; // NOTREACHED + } + + // A new photo was uploaded. Store it and save some important details + // in App::$data for use in the cropping function + + + $hash = photo_new_resource(); + $smallest = 0; + + require_once('include/attach.php'); + + $res = attach_store(\App::get_channel(), get_observer_hash(), '', array('album' => t('Profile Photos'), 'hash' => $hash)); + + logger('attach_store: ' . print_r($res,true)); + + if($res && intval($res['data']['is_photo'])) { + $i = q("select * from photo where resource_id = '%s' and uid = %d order by imgscale", + dbesc($hash), + intval(local_channel()) + ); + + if(! $i) { + notice( t('Image upload failed.') . EOL ); + return; + } + $os_storage = false; + + foreach($i as $ii) { + if(intval($ii['imgscale']) < PHOTO_RES_640) { + $smallest = intval($ii['imgscale']); + $os_storage = intval($ii['os_storage']); + $imagedata = $ii['content']; + $filetype = $ii['mimetype']; + } + } + } + + $imagedata = (($os_storage) ? @file_get_contents($imagedata) : $imagedata); + $ph = photo_factory($imagedata, $filetype); + + if(! $ph->is_valid()) { + notice( t('Unable to process image.') . EOL ); + return; + } + + return $this->profile_photo_crop_ui_head($a, $ph, $hash, $smallest); + + // This will "fall through" to the get() method, and since + // App::$data['imagecrop'] is set, it will proceed to cropping + // rather than present the upload form + } + + + /* @brief Generate content of profile-photo view + * + * @param $a Current application + * @return void + * + */ + + + function get() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL ); + return; + } + + $channel = \App::get_channel(); + + $newuser = false; + + if(argc() == 2 && argv(1) === 'new') + $newuser = true; + + if(argv(1) === 'use') { + if (argc() < 3) { + notice( t('Permission denied.') . EOL ); + return; + }; + + $resource_id = argv(2); + + // When using an existing photo, we don't have a dialogue to offer a choice of profiles, + // so it gets attached to the default + + $p = q("select id from profile where is_default = 1 and uid = %d", + intval(local_channel()) + ); + if($p) { + $_REQUEST['profile'] = $p[0]['id']; + } + + + $r = q("SELECT id, album, imgscale FROM photo WHERE uid = %d AND resource_id = '%s' ORDER BY imgscale ASC", + intval(local_channel()), + dbesc($resource_id) + ); + if(! $r) { + notice( t('Photo not available.') . EOL ); + return; + } + $havescale = false; + foreach($r as $rr) { + if($rr['imgscale'] == PHOTO_RES_PROFILE_80) + $havescale = true; + } + + // set an already loaded and cropped photo as profile photo + + if(($r[0]['album'] == t('Profile Photos')) && ($havescale)) { + // unset any existing profile photos + $r = q("UPDATE photo SET photo_usage = %d WHERE photo_usage = %d AND uid = %d", + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + intval(local_channel())); + + $r = q("UPDATE photo SET photo_usage = %d WHERE uid = %d AND resource_id = '%s'", + intval(PHOTO_PROFILE), + intval(local_channel()), + dbesc($resource_id) + ); + + $r = q("UPDATE xchan set xchan_photo_date = '%s' + where xchan_hash = '%s'", + dbesc(datetime_convert()), + dbesc($channel['xchan_hash']) + ); + + profile_photo_set_profile_perms(local_channel()); // Reset default photo permissions to public + \Zotlabs\Daemon\Master::Summon(array('Directory',local_channel())); + goaway(z_root() . '/profiles'); + } + + $r = q("SELECT content, mimetype, resource_id, os_storage FROM photo WHERE id = %d and uid = %d limit 1", + intval($r[0]['id']), + intval(local_channel()) + + ); + if(! $r) { + notice( t('Photo not available.') . EOL ); + return; + } + + if(intval($r[0]['os_storage'])) + $data = @file_get_contents($r[0]['content']); + else + $data = dbunescbin($r[0]['content']); + + $ph = photo_factory($data, $r[0]['mimetype']); + $smallest = 0; + if($ph->is_valid()) { + // go ahead as if we have just uploaded a new photo to crop + $i = q("select resource_id, imgscale from photo where resource_id = '%s' and uid = %d order by imgscale", + dbesc($r[0]['resource_id']), + intval(local_channel()) + ); + + if($i) { + $hash = $i[0]['resource_id']; + foreach($i as $ii) { + if(intval($ii['imgscale']) < PHOTO_RES_640) { + $smallest = intval($ii['imgscale']); + } + } + } + } + + $this->profile_photo_crop_ui_head($a, $ph, $hash, $smallest); + + // falls through with App::$data['imagecrop'] set so we go straight to the cropping section + } + + + // present an upload form + + $profiles = q("select id, profile_name as name, is_default from profile where uid = %d order by id asc", + intval(local_channel()) + ); + + if(! x(\App::$data,'imagecrop')) { + + $tpl = get_markup_template('profile_photo.tpl'); + + $o .= replace_macros($tpl,array( + '$user' => \App::$channel['channel_address'], + '$lbl_upfile' => t('Upload File:'), + '$lbl_profiles' => t('Select a profile:'), + '$title' => t('Upload Profile Photo'), + '$submit' => t('Upload'), + '$profiles' => $profiles, + '$single' => ((count($profiles) == 1) ? true : false), + '$profile0' => $profiles[0], + '$form_security_token' => get_form_security_token("profile_photo"), + // FIXME - yuk + '$select' => sprintf('%s %s', t('or'), ($newuser) ? '' . t('skip this step') . '' : '' . t('select a photo from your photo albums') . '') + )); + + call_hooks('profile_photo_content_end', $o); + + return $o; + } + else { + + // present a cropping form + + $filename = \App::$data['imagecrop'] . '-' . \App::$data['imagecrop_resolution']; + $resolution = \App::$data['imagecrop_resolution']; + $tpl = get_markup_template("cropbody.tpl"); + $o .= replace_macros($tpl,array( + '$filename' => $filename, + '$profile' => intval($_REQUEST['profile']), + '$resource' => \App::$data['imagecrop'] . '-' . \App::$data['imagecrop_resolution'], + '$image_url' => z_root() . '/photo/' . $filename, + '$title' => t('Crop Image'), + '$desc' => t('Please adjust the image cropping for optimum viewing.'), + '$form_security_token' => get_form_security_token("profile_photo"), + '$done' => t('Done Editing') + )); + return $o; + } + + return; // NOTREACHED + } + + /* @brief Generate the UI for photo-cropping + * + * @param $a Current application + * @param $ph Photo-Factory + * @return void + * + */ + + + + function profile_photo_crop_ui_head(&$a, $ph, $hash, $smallest){ + + $max_length = get_config('system','max_image_length'); + if(! $max_length) + $max_length = MAX_IMAGE_LENGTH; + if($max_length > 0) + $ph->scaleImage($max_length); + + \App::$data['width'] = $ph->getWidth(); + \App::$data['height'] = $ph->getHeight(); + + if(\App::$data['width'] < 500 || \App::$data['height'] < 500) { + $ph->scaleImageUp(400); + \App::$data['width'] = $ph->getWidth(); + \App::$data['height'] = $ph->getHeight(); + } + + + \App::$data['imagecrop'] = $hash; + \App::$data['imagecrop_resolution'] = $smallest; + \App::$page['htmlhead'] .= replace_macros(get_markup_template("crophead.tpl"), array()); + return; + } + + +} diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php new file mode 100644 index 000000000..4b05182c2 --- /dev/null +++ b/Zotlabs/Module/Profiles.php @@ -0,0 +1,789 @@ + 2) && (argv(1) === "drop") && intval(argv(2))) { + $r = q("SELECT * FROM `profile` WHERE `id` = %d AND `uid` = %d AND `is_default` = 0 LIMIT 1", + intval(argv(2)), + intval(local_channel()) + ); + if(! count($r)) { + notice( t('Profile not found.') . EOL); + goaway(z_root() . '/profiles'); + return; // NOTREACHED + } + $profile_guid = $r['profile_guid']; + + check_form_security_token_redirectOnErr('/profiles', 'profile_drop', 't'); + + // move every contact using this profile as their default to the user default + + $r = q("UPDATE abook SET abook_profile = (SELECT profile_guid AS FROM profile WHERE is_default = 1 AND uid = %d LIMIT 1) WHERE abook_profile = '%s' AND abook_channel = %d ", + intval(local_channel()), + dbesc($profile_guid), + intval(local_channel()) + ); + $r = q("DELETE FROM `profile` WHERE `id` = %d AND `uid` = %d", + intval(argv(2)), + intval(local_channel()) + ); + if($r) + info( t('Profile deleted.') . EOL); + + // @fixme this is a much more complicated sync - add any changed abook entries and + // also add deleted flag to profile structure + // profiles_build_sync is just here as a placeholder - it doesn't work at all here + + // profiles_build_sync(local_channel()); + + goaway(z_root() . '/profiles'); + return; // NOTREACHED + } + + + + + + if((argc() > 1) && (argv(1) === 'new')) { + + // check_form_security_token_redirectOnErr('/profiles', 'profile_new', 't'); + + $r0 = q("SELECT `id` FROM `profile` WHERE `uid` = %d", + intval(local_channel())); + $num_profiles = count($r0); + + $name = t('Profile-') . ($num_profiles + 1); + + $r1 = q("SELECT `fullname`, `photo`, `thumb` FROM `profile` WHERE `uid` = %d AND `is_default` = 1 LIMIT 1", + intval(local_channel())); + + $r2 = q("INSERT INTO `profile` (`aid`, `uid` , `profile_guid`, `profile_name` , `fullname`, `photo`, `thumb`) + VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s' )", + intval(get_account_id()), + intval(local_channel()), + dbesc(random_string()), + dbesc($name), + dbesc($r1[0]['fullname']), + dbesc($r1[0]['photo']), + dbesc($r1[0]['thumb']) + ); + + $r3 = q("SELECT `id` FROM `profile` WHERE `uid` = %d AND `profile_name` = '%s' LIMIT 1", + intval(local_channel()), + dbesc($name) + ); + + info( t('New profile created.') . EOL); + if(count($r3) == 1) + goaway(z_root() . '/profiles/' . $r3[0]['id']); + + goaway(z_root() . '/profiles'); + } + + if((argc() > 2) && (argv(1) === 'clone')) { + + check_form_security_token_redirectOnErr('/profiles', 'profile_clone', 't'); + + $r0 = q("SELECT `id` FROM `profile` WHERE `uid` = %d", + intval(local_channel())); + $num_profiles = count($r0); + + $name = t('Profile-') . ($num_profiles + 1); + $r1 = q("SELECT * FROM `profile` WHERE `uid` = %d AND `id` = %d LIMIT 1", + intval(local_channel()), + intval(\App::$argv[2]) + ); + if(! count($r1)) { + notice( t('Profile unavailable to clone.') . EOL); + \App::$error = 404; + return; + } + unset($r1[0]['id']); + $r1[0]['is_default'] = 0; + $r1[0]['publish'] = 0; + $r1[0]['profile_name'] = dbesc($name); + $r1[0]['profile_guid'] = dbesc(random_string()); + + dbesc_array($r1[0]); + + $r2 = dbq("INSERT INTO `profile` (`" + . implode("`, `", array_keys($r1[0])) + . "`) VALUES ('" + . implode("', '", array_values($r1[0])) + . "')" ); + + $r3 = q("SELECT `id` FROM `profile` WHERE `uid` = %d AND `profile_name` = '%s' LIMIT 1", + intval(local_channel()), + dbesc($name) + ); + info( t('New profile created.') . EOL); + + profiles_build_sync(local_channel()); + + if(($r3) && (count($r3) == 1)) + goaway(z_root() . '/profiles/' . $r3[0]['id']); + + goaway(z_root() . '/profiles'); + + return; // NOTREACHED + } + + if((argc() > 2) && (argv(1) === 'export')) { + + $r1 = q("SELECT * FROM `profile` WHERE `uid` = %d AND `id` = %d LIMIT 1", + intval(local_channel()), + intval(argv(2)) + ); + if(! $r1) { + notice( t('Profile unavailable to export.') . EOL); + \App::$error = 404; + return; + } + header('content-type: application/octet_stream'); + header('content-disposition: attachment; filename="' . $r1[0]['profile_name'] . '.json"' ); + + unset($r1[0]['id']); + unset($r1[0]['aid']); + unset($r1[0]['uid']); + unset($r1[0]['is_default']); + unset($r1[0]['publish']); + unset($r1[0]['profile_name']); + unset($r1[0]['profile_guid']); + echo json_encode($r1[0]); + killme(); + } + + + + + // Run profile_load() here to make sure the theme is set before + // we start loading content + if(((argc() > 1) && (intval(argv(1)))) || !feature_enabled(local_channel(),'multi_profiles')) { + if(feature_enabled(local_channel(),'multi_profiles')) + $id = \App::$argv[1]; + else { + $x = q("select id from profile where uid = %d and is_default = 1", + intval(local_channel()) + ); + if($x) + $id = $x[0]['id']; + } + $r = q("SELECT * FROM `profile` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($id), + intval(local_channel()) + ); + if(! count($r)) { + notice( t('Profile not found.') . EOL); + \App::$error = 404; + return; + } + + $chan = \App::get_channel(); + + profile_load($chan['channel_address'],$r[0]['id']); + } + } + + function post() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + require_once('include/activities.php'); + + $namechanged = false; + + + // import from json export file. + // Only import fields that are allowed on this hub + + if(x($_FILES,'userfile')) { + $src = $_FILES['userfile']['tmp_name']; + $filesize = intval($_FILES['userfile']['size']); + if($filesize) { + $j = @json_decode(@file_get_contents($src),true); + @unlink($src); + if($j) { + $fields = get_profile_fields_advanced(); + if($fields) { + foreach($j as $jj => $v) { + foreach($fields as $f => $n) { + if($jj == $f) { + $_POST[$f] = $v; + break; + } + } + } + } + } + } + } + + call_hooks('profile_post', $_POST); + + + if((argc() > 1) && (argv(1) !== "new") && intval(argv(1))) { + $orig = q("SELECT * FROM `profile` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval(\App::$argv[1]), + intval(local_channel()) + ); + if(! count($orig)) { + notice( t('Profile not found.') . EOL); + return; + } + + check_form_security_token_redirectOnErr('/profiles', 'profile_edit'); + + $is_default = (($orig[0]['is_default']) ? 1 : 0); + + $profile_name = notags(trim($_POST['profile_name'])); + if(! strlen($profile_name)) { + notice( t('Profile Name is required.') . EOL); + return; + } + + $dob = $_POST['dob'] ? escape_tags(trim($_POST['dob'])) : '0000-00-00'; // FIXME: Needs to be validated? + + $y = substr($dob,0,4); + if((! ctype_digit($y)) || ($y < 1900)) + $ignore_year = true; + else + $ignore_year = false; + + if($dob != '0000-00-00') { + if(strpos($dob,'0000-') === 0) { + $ignore_year = true; + $dob = substr($dob,5); + } + $dob = datetime_convert('UTC','UTC',(($ignore_year) ? '1900-' . $dob : $dob),(($ignore_year) ? 'm-d' : 'Y-m-d')); + if($ignore_year) + $dob = '0000-' . $dob; + } + + $name = escape_tags(trim($_POST['name'])); + + if($orig[0]['fullname'] != $name) { + $namechanged = true; + + $v = validate_channelname($name); + if($v) { + notice($v); + $namechanged = false; + $name = $orig[0]['fullname']; + } + } + + $pdesc = escape_tags(trim($_POST['pdesc'])); + $gender = escape_tags(trim($_POST['gender'])); + $address = escape_tags(trim($_POST['address'])); + $locality = escape_tags(trim($_POST['locality'])); + $region = escape_tags(trim($_POST['region'])); + $postal_code = escape_tags(trim($_POST['postal_code'])); + $country_name = escape_tags(trim($_POST['country_name'])); + $keywords = escape_tags(trim($_POST['keywords'])); + $marital = escape_tags(trim($_POST['marital'])); + $howlong = escape_tags(trim($_POST['howlong'])); + $sexual = escape_tags(trim($_POST['sexual'])); + $homepage = escape_tags(trim($_POST['homepage'])); + $hometown = escape_tags(trim($_POST['hometown'])); + $politic = escape_tags(trim($_POST['politic'])); + $religion = escape_tags(trim($_POST['religion'])); + + $likes = fix_mce_lf(escape_tags(trim($_POST['likes']))); + $dislikes = fix_mce_lf(escape_tags(trim($_POST['dislikes']))); + + $about = fix_mce_lf(escape_tags(trim($_POST['about']))); + $interest = fix_mce_lf(escape_tags(trim($_POST['interest']))); + $contact = fix_mce_lf(escape_tags(trim($_POST['contact']))); + $channels = fix_mce_lf(escape_tags(trim($_POST['channels']))); + $music = fix_mce_lf(escape_tags(trim($_POST['music']))); + $book = fix_mce_lf(escape_tags(trim($_POST['book']))); + $tv = fix_mce_lf(escape_tags(trim($_POST['tv']))); + $film = fix_mce_lf(escape_tags(trim($_POST['film']))); + $romance = fix_mce_lf(escape_tags(trim($_POST['romance']))); + $work = fix_mce_lf(escape_tags(trim($_POST['work']))); + $education = fix_mce_lf(escape_tags(trim($_POST['education']))); + + $hide_friends = ((intval($_POST['hide_friends'])) ? 1: 0); + + require_once('include/text.php'); + linkify_tags($a, $likes, local_channel()); + linkify_tags($a, $dislikes, local_channel()); + linkify_tags($a, $about, local_channel()); + linkify_tags($a, $interest, local_channel()); + linkify_tags($a, $interest, local_channel()); + linkify_tags($a, $contact, local_channel()); + linkify_tags($a, $channels, local_channel()); + linkify_tags($a, $music, local_channel()); + linkify_tags($a, $book, local_channel()); + linkify_tags($a, $tv, local_channel()); + linkify_tags($a, $film, local_channel()); + linkify_tags($a, $romance, local_channel()); + linkify_tags($a, $work, local_channel()); + linkify_tags($a, $education, local_channel()); + + + $with = ((x($_POST,'with')) ? escape_tags(trim($_POST['with'])) : ''); + + if(! strlen($howlong)) + $howlong = NULL_DATE; + else + $howlong = datetime_convert(date_default_timezone_get(),'UTC',$howlong); + + // linkify the relationship target if applicable + + $withchanged = false; + + if(strlen($with)) { + if($with != strip_tags($orig[0]['partner'])) { + $withchanged = true; + $prf = ''; + $lookup = $with; + if(strpos($lookup,'@') === 0) + $lookup = substr($lookup,1); + $lookup = str_replace('_',' ', $lookup); + $newname = $lookup; + + $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash WHERE xchan_name = '%s' AND abook_channel = %d LIMIT 1", + dbesc($newname), + intval(local_channel()) + ); + if(! $r) { + $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash WHERE xchan_addr = '%s' AND abook_channel = %d LIMIT 1", + dbesc($lookup . '@%'), + intval(local_channel()) + ); + } + if($r) { + $prf = $r[0]['xchan_url']; + $newname = $r[0]['xchan_name']; + } + + + if($prf) { + $with = str_replace($lookup,'' . $newname . '', $with); + if(strpos($with,'@') === 0) + $with = substr($with,1); + } + } + else + $with = $orig[0]['partner']; + } + + $profile_fields_basic = get_profile_fields_basic(); + $profile_fields_advanced = get_profile_fields_advanced(); + $advanced = ((feature_enabled(local_channel(),'advanced_profiles')) ? true : false); + if($advanced) + $fields = $profile_fields_advanced; + else + $fields = $profile_fields_basic; + + $z = q("select * from profdef where true"); + if($z) { + foreach($z as $zz) { + if(array_key_exists($zz['field_name'],$fields)) { + $w = q("select * from profext where channel_id = %d and hash = '%s' and k = '%s' limit 1", + intval(local_channel()), + dbesc($orig[0]['profile_guid']), + dbesc($zz['field_name']) + ); + if($w) { + q("update profext set v = '%s' where id = %d", + dbesc(escape_tags(trim($_POST[$zz['field_name']]))), + intval($w[0]['id']) + ); + } + else { + q("insert into profext ( channel_id, hash, k, v ) values ( %d, '%s', '%s', '%s') ", + intval(local_channel()), + dbesc($orig[0]['profile_guid']), + dbesc($zz['field_name']), + dbesc(escape_tags(trim($_POST[$zz['field_name']]))) + ); + } + } + } + } + + $changes = array(); + $value = ''; + if($is_default) { + if($marital != $orig[0]['marital']) { + $changes[] = '[color=#ff0000]♥[/color] ' . t('Marital Status'); + $value = $marital; + } + if($withchanged) { + $changes[] = '[color=#ff0000]♥[/color] ' . t('Romantic Partner'); + $value = strip_tags($with); + } + if($likes != $orig[0]['likes']) { + $changes[] = t('Likes'); + $value = $likes; + } + if($dislikes != $orig[0]['dislikes']) { + $changes[] = t('Dislikes'); + $value = $dislikes; + } + if($work != $orig[0]['employment']) { + $changes[] = t('Work/Employment'); + } + if($religion != $orig[0]['religion']) { + $changes[] = t('Religion'); + $value = $religion; + } + if($politic != $orig[0]['politic']) { + $changes[] = t('Political Views'); + $value = $politic; + } + if($gender != $orig[0]['gender']) { + $changes[] = t('Gender'); + $value = $gender; + } + if($sexual != $orig[0]['sexual']) { + $changes[] = t('Sexual Preference'); + $value = $sexual; + } + if($homepage != $orig[0]['homepage']) { + $changes[] = t('Homepage'); + $value = $homepage; + } + if($interest != $orig[0]['interest']) { + $changes[] = t('Interests'); + $value = $interest; + } + if($address != $orig[0]['address']) { + $changes[] = t('Address'); + // New address not sent in notifications, potential privacy issues + // in case this leaks to unintended recipients. Yes, it's in the public + // profile but that doesn't mean we have to broadcast it to everybody. + } + if($locality != $orig[0]['locality'] || $region != $orig[0]['region'] + || $country_name != $orig[0]['country_name']) { + $changes[] = t('Location'); + $comma1 = ((($locality) && ($region || $country_name)) ? ', ' : ' '); + $comma2 = (($region && $country_name) ? ', ' : ''); + $value = $locality . $comma1 . $region . $comma2 . $country_name; + } + + profile_activity($changes,$value); + + } + + $r = q("UPDATE `profile` + SET `profile_name` = '%s', + `fullname` = '%s', + `pdesc` = '%s', + `gender` = '%s', + `dob` = '%s', + `address` = '%s', + `locality` = '%s', + `region` = '%s', + `postal_code` = '%s', + `country_name` = '%s', + `marital` = '%s', + `partner` = '%s', + `howlong` = '%s', + `sexual` = '%s', + `homepage` = '%s', + `hometown` = '%s', + `politic` = '%s', + `religion` = '%s', + `keywords` = '%s', + `likes` = '%s', + `dislikes` = '%s', + `about` = '%s', + `interest` = '%s', + `contact` = '%s', + `channels` = '%s', + `music` = '%s', + `book` = '%s', + `tv` = '%s', + `film` = '%s', + `romance` = '%s', + `employment` = '%s', + `education` = '%s', + `hide_friends` = %d + WHERE `id` = %d AND `uid` = %d", + dbesc($profile_name), + dbesc($name), + dbesc($pdesc), + dbesc($gender), + dbesc($dob), + dbesc($address), + dbesc($locality), + dbesc($region), + dbesc($postal_code), + dbesc($country_name), + dbesc($marital), + dbesc($with), + dbesc($howlong), + dbesc($sexual), + dbesc($homepage), + dbesc($hometown), + dbesc($politic), + dbesc($religion), + dbesc($keywords), + dbesc($likes), + dbesc($dislikes), + dbesc($about), + dbesc($interest), + dbesc($contact), + dbesc($channels), + dbesc($music), + dbesc($book), + dbesc($tv), + dbesc($film), + dbesc($romance), + dbesc($work), + dbesc($education), + intval($hide_friends), + intval(argv(1)), + intval(local_channel()) + ); + + if($r) + info( t('Profile updated.') . EOL); + + $r = q("select * from profile where id = %d and uid = %d limit 1", + intval(argv(1)), + intval(local_channel()) + ); + if($r) { + require_once('include/zot.php'); + build_sync_packet(local_channel(),array('profile' => $r)); + } + + $channel = \App::get_channel(); + + if($namechanged && $is_default) { + $r = q("UPDATE xchan SET xchan_name = '%s', xchan_name_date = '%s' WHERE xchan_hash = '%s'", + dbesc($name), + dbesc(datetime_convert()), + dbesc($channel['xchan_hash']) + ); + $r = q("UPDATE channel SET channel_name = '%s' WHERE channel_hash = '%s'", + dbesc($name), + dbesc($channel['xchan_hash']) + ); + } + + if($is_default) { + // reload the info for the sidebar widget - why does this not work? + profile_load($channel['channel_address']); + \Zotlabs\Daemon\Master::Summon(array('Directory',local_channel())); + } + } + } + + + function get() { + + $o = ''; + + $channel = \App::get_channel(); + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + require_once('include/channel.php'); + + $profile_fields_basic = get_profile_fields_basic(); + $profile_fields_advanced = get_profile_fields_advanced(); + + if(((argc() > 1) && (intval(argv(1)))) || !feature_enabled(local_channel(),'multi_profiles')) { + if(feature_enabled(local_channel(),'multi_profiles')) + $id = \App::$argv[1]; + else { + $x = q("select id from profile where uid = %d and is_default = 1", + intval(local_channel()) + ); + if($x) + $id = $x[0]['id']; + } + $r = q("SELECT * FROM `profile` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($id), + intval(local_channel()) + ); + if(! count($r)) { + notice( t('Profile not found.') . EOL); + return; + } + + $editselect = 'none'; + + \App::$page['htmlhead'] .= replace_macros(get_markup_template('profed_head.tpl'), array( + '$baseurl' => z_root(), + '$editselect' => $editselect, + )); + + $advanced = ((feature_enabled(local_channel(),'advanced_profiles')) ? true : false); + if($advanced) + $fields = $profile_fields_advanced; + else + $fields = $profile_fields_basic; + + $hide_friends = array( + 'hide_friends', + t('Hide your connections list from viewers of this profile'), + $r[0]['hide_friends'], + '', + array(t('No'),t('Yes')) + ); + + $q = q("select * from profdef where true"); + if($q) { + $extra_fields = array(); + + foreach($q as $qq) { + $mine = q("select v from profext where k = '%s' and hash = '%s' and channel_id = %d limit 1", + dbesc($qq['field_name']), + dbesc($r[0]['profile_guid']), + intval(local_channel()) + ); + + if(array_key_exists($qq['field_name'],$fields)) { + $extra_fields[] = array($qq['field_name'],$qq['field_desc'],(($mine) ? $mine[0]['v'] : ''), $qq['field_help']); + } + } + } + + //logger('extra_fields: ' . print_r($extra_fields,true)); + + $f = get_config('system','birthday_input_format'); + if(! $f) + $f = 'ymd'; + + $is_default = (($r[0]['is_default']) ? 1 : 0); + + $tpl = get_markup_template("profile_edit.tpl"); + $o .= replace_macros($tpl,array( + + '$form_security_token' => get_form_security_token("profile_edit"), + '$profile_clone_link' => ((feature_enabled(local_channel(),'multi_profiles')) ? 'profiles/clone/' . $r[0]['id'] . '?t=' + . get_form_security_token("profile_clone") : ''), + '$profile_drop_link' => 'profiles/drop/' . $r[0]['id'] . '?t=' + . get_form_security_token("profile_drop"), + + '$fields' => $fields, + '$guid' => $r[0]['profile_guid'], + '$banner' => t('Edit Profile Details'), + '$submit' => t('Submit'), + '$viewprof' => t('View this profile'), + '$editvis' => t('Edit visibility'), + '$tools_label' => t('Profile Tools'), + '$coverpic' => t('Change cover photo'), + '$profpic' => t('Change profile photo'), + '$cr_prof' => t('Create a new profile using these settings'), + '$cl_prof' => t('Clone this profile'), + '$del_prof' => t('Delete this profile'), + '$addthing' => t('Add profile things'), + '$personal' => t('Personal'), + '$location' => t('Location'), + '$relation' => t('Relation'), + '$miscellaneous'=> t('Miscellaneous'), + '$exportable' => feature_enabled(local_channel(),'profile_export'), + '$lbl_import' => t('Import profile from file'), + '$lbl_export' => t('Export profile to file'), + '$lbl_gender' => t('Your gender'), + '$lbl_marital' => t('Marital status'), + '$lbl_sexual' => t('Sexual preference'), + '$baseurl' => z_root(), + '$profile_id' => $r[0]['id'], + '$profile_name' => array('profile_name', t('Profile name'), $r[0]['profile_name'], t('Required'), '*'), + '$is_default' => $is_default, + '$default' => t('This is your default profile.') . EOL . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))), + '$advanced' => $advanced, + '$name' => array('name', t('Your full name'), $r[0]['fullname'], t('Required'), '*'), + '$pdesc' => array('pdesc', t('Title/Description'), $r[0]['pdesc']), + '$dob' => dob($r[0]['dob']), + '$hide_friends' => $hide_friends, + '$address' => array('address', t('Street address'), $r[0]['address']), + '$locality' => array('locality', t('Locality/City'), $r[0]['locality']), + '$region' => array('region', t('Region/State'), $r[0]['region']), + '$postal_code' => array('postal_code', t('Postal/Zip code'), $r[0]['postal_code']), + '$country_name' => array('country_name', t('Country'), $r[0]['country_name']), + '$gender' => gender_selector($r[0]['gender']), + '$gender_min' => gender_selector_min($r[0]['gender']), + '$marital' => marital_selector($r[0]['marital']), + '$marital_min' => marital_selector_min($r[0]['marital']), + '$with' => array('with', t("Who (if applicable)"), $r[0]['partner'], t('Examples: cathy123, Cathy Williams, cathy@example.com')), + '$howlong' => array('howlong', t('Since (date)'), ($r[0]['howlong'] === NULL_DATE ? '' : datetime_convert('UTC',date_default_timezone_get(),$r[0]['howlong']))), + '$sexual' => sexpref_selector($r[0]['sexual']), + '$sexual_min' => sexpref_selector_min($r[0]['sexual']), + '$about' => array('about', t('Tell us about yourself'), $r[0]['about']), + '$homepage' => array('homepage', t('Homepage URL'), $r[0]['homepage']), + '$hometown' => array('hometown', t('Hometown'), $r[0]['hometown']), + '$politic' => array('politic', t('Political views'), $r[0]['politic']), + '$religion' => array('religion', t('Religious views'), $r[0]['religion']), + '$keywords' => array('keywords', t('Keywords used in directory listings'), $r[0]['keywords'], t('Example: fishing photography software')), + '$likes' => array('likes', t('Likes'), $r[0]['likes']), + '$dislikes' => array('dislikes', t('Dislikes'), $r[0]['dislikes']), + '$music' => array('music', t('Musical interests'), $r[0]['music']), + '$book' => array('book', t('Books, literature'), $r[0]['book']), + '$tv' => array('tv', t('Television'), $r[0]['tv']), + '$film' => array('film', t('Film/Dance/Culture/Entertainment'), $r[0]['film']), + '$interest' => array('interest', t('Hobbies/Interests'), $r[0]['interest']), + '$romance' => array('romance',t('Love/Romance'), $r[0]['romance']), + '$work' => array('work', t('Work/Employment'), $r[0]['employment']), + '$education' => array('education', t('School/Education'), $r[0]['education']), + '$contact' => array('contact', t('Contact information and social networks'), $r[0]['contact']), + '$channels' => array('channels', t('My other channels'), $r[0]['channels']), + '$extra_fields' => $extra_fields, + )); + + $arr = array('profile' => $r[0], 'entry' => $o); + call_hooks('profile_edit', $arr); + + return $o; + } + else { + + $r = q("SELECT * FROM `profile` WHERE `uid` = %d", + local_channel()); + if($r) { + + $tpl = get_markup_template('profile_entry.tpl'); + foreach($r as $rr) { + $profiles .= replace_macros($tpl, array( + '$photo' => $rr['thumb'], + '$id' => $rr['id'], + '$alt' => t('Profile Image'), + '$profile_name' => $rr['profile_name'], + '$visible' => (($rr['is_default']) + ? '' . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))) . '' + : '' . t('Edit visibility') . '') + )); + } + + $tpl_header = get_markup_template('profile_listing_header.tpl'); + $o .= replace_macros($tpl_header,array( + '$header' => t('Edit Profiles'), + '$cr_new' => t('Create New'), + '$cr_new_link' => 'profiles/new?t=' . get_form_security_token("profile_new"), + '$profiles' => $profiles + )); + + } + return $o; + } + + } + +} diff --git a/Zotlabs/Module/Profperm.php b/Zotlabs/Module/Profperm.php new file mode 100644 index 000000000..b1da147c1 --- /dev/null +++ b/Zotlabs/Module/Profperm.php @@ -0,0 +1,172 @@ + 2) && intval(argv(1)) && intval(argv(2))) { + $r = q("SELECT abook_id FROM abook WHERE abook_id = %d and abook_channel = %d limit 1", + intval(argv(2)), + intval(local_channel()) + ); + if($r) + $change = intval(argv(2)); + } + + + if((argc() > 1) && (intval(argv(1)))) { + $r = q("SELECT * FROM `profile` WHERE `id` = %d AND `uid` = %d AND `is_default` = 0 LIMIT 1", + intval(argv(1)), + intval(local_channel()) + ); + if(! $r) { + notice( t('Invalid profile identifier.') . EOL ); + return; + } + + $profile = $r[0]; + + $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d AND abook_profile = '%s'", + intval(local_channel()), + dbesc($profile['profile_guid']) + ); + + $ingroup = array(); + if($r) + foreach($r as $member) + $ingroup[] = $member['abook_id']; + + $members = $r; + + if($change) { + if(in_array($change,$ingroup)) { + q("UPDATE abook SET abook_profile = '' WHERE abook_id = %d AND abook_channel = %d", + intval($change), + intval(local_channel()) + ); + } + else { + q("UPDATE abook SET abook_profile = '%s' WHERE abook_id = %d AND abook_channel = %d", + dbesc($profile['profile_guid']), + intval($change), + intval(local_channel()) + ); + + } + + + //Time to update the permissions on the profile-pictures as well + + profile_photo_set_profile_perms(local_channel(),$profile['id']); + + $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d AND abook_profile = '%s'", + intval(local_channel()), + dbesc($profile['profile_guid']) + ); + + $members = $r; + + $ingroup = array(); + if(count($r)) + foreach($r as $member) + $ingroup[] = $member['abook_id']; + } + + $o .= '

    ' . t('Profile Visibility Editor') . '

    '; + + $o .= '

    ' . t('Profile') . ' \'' . $profile['profile_name'] . '\'

    '; + + $o .= '
    ' . t('Click on a contact to add or remove.') . '
    '; + + } + + $o .= '
    '; + if($change) + $o = ''; + + $o .= '
    '; + $o .= '

    ' . t('Visible To') . '

    '; + $o .= '
    '; + $o .= '
    '; + + $textmode = (($switchtotext && (count($members) > $switchtotext)) ? true : false); + + foreach($members as $member) { + if($member['xchan_url']) { + $member['click'] = 'profChangeMember(' . $profile['id'] . ',' . $member['abook_id'] . '); return false;'; + $o .= micropro($member,true,'mpprof', $textmode); + } + } + $o .= '
    '; + $o .= '
    '; + + $o .= '
    '; + $o .= '

    ' . t("All Connections") . '

    '; + $o .= '
    '; + $o .= '
    '; + + $r = abook_connections(local_channel()); + + if($r) { + $textmode = (($switchtotext && (count($r) > $switchtotext)) ? true : false); + foreach($r as $member) { + if(! in_array($member['abook_id'],$ingroup)) { + $member['click'] = 'profChangeMember(' . $profile['id'] . ',' . $member['abook_id'] . '); return false;'; + $o .= micropro($member,true,'mpprof',$textmode); + } + } + } + + $o .= '
    '; + + if($change) { + echo $o; + killme(); + } + $o .= '
    '; + return $o; + + } + + +} diff --git a/Zotlabs/Module/Pubsites.php b/Zotlabs/Module/Pubsites.php new file mode 100644 index 000000000..0dda08e6d --- /dev/null +++ b/Zotlabs/Module/Pubsites.php @@ -0,0 +1,60 @@ +'; + + $o .= '

    ' . t('Public Hubs') . '

    '; + + $o .= '
    ' . + t('The listed hubs allow public registration for the $Projectname network. All hubs in the network are interlinked so membership on any of them conveys membership in the network as a whole. Some hubs may require subscription or provide tiered service plans. The hub itself may provide additional details.') . '
    ' . EOL; + + $ret = z_fetch_url($url); + if($ret['success']) { + $j = json_decode($ret['body'],true); + if($j) { + $o .= ''; + if($j['sites']) { + foreach($j['sites'] as $jj) { + $m = parse_url($jj['url']); + if(strpos($jj['project'],\Zotlabs\Lib\System::get_platform_name()) === false) + continue; + $host = strtolower(substr($jj['url'],strpos($jj['url'],'://')+3)); + $rate_links = ((local_channel()) ? '' : ''); + $location = ''; + if(!empty($jj['location'])) { + $location = '

    ' . $jj['location'] . '

    '; + } + else { + $location = '
     '; + } + $urltext = str_replace(array('https://'), '', $jj['url']); + $o .= '' . $rate_links . ''; + } + } + + $o .= '
    ' . t('Hub URL') . '' . t('Access Type') . '' . t('Registration Policy') . '' . t('Stats') . '' . t('Software') . '' . t('Ratings') . '
    ' . t('Rate') . '
    ' . $urltext . '' . $location . '' . $jj['access'] . '' . $jj['register'] . '' . '' . ucwords($jj['project']) . ' ' . t('View') . '
    '; + + $o .= '
    '; + + } + } + return $o; + } + +} diff --git a/Zotlabs/Module/Pubstream.php b/Zotlabs/Module/Pubstream.php new file mode 100644 index 000000000..312be7718 --- /dev/null +++ b/Zotlabs/Module/Pubstream.php @@ -0,0 +1,168 @@ +' . "\r\n"; + $o .= "\r\n"; + + \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + '$baseurl' => z_root(), + '$pgtype' => 'pubstream', + '$uid' => ((local_channel()) ? local_channel() : '0'), + '$gid' => '0', + '$cid' => '0', + '$cmin' => '0', + '$cmax' => '99', + '$star' => '0', + '$liked' => '0', + '$conv' => '0', + '$spam' => '0', + '$fh' => '1', + '$nouveau' => '0', + '$wall' => '0', + '$list' => '0', + '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$search' => '', + '$order' => 'comment', + '$file' => '', + '$cats' => '', + '$tags' => '', + '$dend' => '', + '$mid' => '', + '$verb' => '', + '$dbegin' => '' + )); + } + + if($update && ! $load) { + // only setup pagination on initial page view + $pager_sql = ''; + } + else { + \App::set_pager_itemspage(20); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + } + + require_once('include/channel.php'); + require_once('include/security.php'); + + if(get_config('system','site_firehose')) { + $uids = " and item.uid in ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) and item_private = 0 and item_wall = 1 "; + } + else { + $sys = get_sys_channel(); + $uids = " and item.uid = " . intval($sys['channel_id']) . " "; + $sql_extra = item_permissions_sql($sys['channel_id']); + \App::$data['firehose'] = intval($sys['channel_id']); + } + + if(get_config('system','public_list_mode')) + $page_mode = 'list'; + else + $page_mode = 'client'; + + + $simple_update = (($update) ? " and item.item_unseen = 1 " : ''); + + if($update && $_SESSION['loadtime']) + $simple_update = " AND (( item_unseen = 1 AND item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) OR item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) "; + if($load) + $simple_update = ''; + + //logger('update: ' . $update . ' load: ' . $load); + + if($update) { + + $ordering = "commented"; + + if($load) { + + // Fetch a page full of parent items for this page + + $r = q("SELECT distinct item.id AS item_id, $ordering FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE true $uids $item_normal + AND item.parent = item.id + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets + ORDER BY $ordering DESC $pager_sql " + ); + + + } + elseif($update) { + + $r = q("SELECT distinct item.id AS item_id, $ordering FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE true $uids $item_normal + AND item.parent = item.id $simple_update + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets" + ); + $_SESSION['loadtime'] = datetime_convert(); + } + // Then fetch all the children of the parents that are on this page + $parents_str = ''; + $update_unseen = ''; + + if($r) { + + $parents_str = ids_to_querystr($r,'item_id'); + + $items = q("SELECT item.*, item.id AS item_id FROM item + WHERE true $uids $item_normal + AND item.parent IN ( %s ) + $sql_extra ", + dbesc($parents_str) + ); + + xchan_query($items,true,(-1)); + $items = fetch_post_tags($items,true); + $items = conv_sort($items,$ordering); + } + else { + $items = array(); + } + + } + + // fake it + $mode = ('network'); + + $o .= conversation($a,$items,$mode,$update,$page_mode); + + if(($items) && (! $update)) + $o .= alt_pager($a,count($items)); + + return $o; + + } +} diff --git a/Zotlabs/Module/README.md b/Zotlabs/Module/README.md new file mode 100644 index 000000000..3b870dd7b --- /dev/null +++ b/Zotlabs/Module/README.md @@ -0,0 +1,80 @@ +Zotlabs/Module +============== + + +This directory contains controller modules for handling web requests. The +lowercase class name indicates the head of the URL path which this module +handles. There are other methods of attaching (routing) URL paths to +controllers, but this is the primary method used in this project. + +Module controllers MUST reside in this directory and namespace to be +autoloaded (unless other specific routing methods are employed). They +typically use and extend the class definition in Zotlabs/Web/Controller +as a template. + +Template: + + 10) + $rating = 10; + + $rating_text = trim(escape_tags($_REQUEST['rating_text'])); + + $signed = \App::$data['target'] . '.' . $rating . '.' . $rating_text; + + $sig = base64url_encode(rsa_sign($signed,$channel['channel_prvkey'])); + + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc(\App::$data['target']) + ); + + if($z) { + $record = $z[0]['xlink_id']; + $w = q("update xlink set xlink_rating = '%d', xlink_rating_text = '%s', xlink_sig = '%s', xlink_updated = '%s' + where xlink_id = %d", + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()), + intval($record) + ); + } + else { + $w = q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 1 ) ", + dbesc($channel['channel_hash']), + dbesc(\App::$data['target']), + intval($rating), + dbesc($rating_text), + dbesc($sig), + dbesc(datetime_convert()) + ); + $z = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1 limit 1", + dbesc($channel['channel_hash']), + dbesc(\App::$data['target']) + ); + if($z) + $record = $z[0]['xlink_id']; + } + + if($record) { + \Zotlabs\Daemon\Master::Summon(array('Ratenotif','rating',$record)); + } + + } + + function get() { + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + return; + } + + // if(! \App::$data['target']) { + // notice( t('No recipients.') . EOL); + // return; + // } + + $poco_rating = get_config('system','poco_rating_enable'); + if((! $poco_rating) && ($poco_rating !== false)) { + notice('Ratings are disabled on this site.'); + return; + } + + $channel = \App::get_channel(); + + $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 1", + dbesc($channel['channel_hash']), + dbesc(\App::$data['target']) + ); + if($r) { + \App::$data['xlink'] = $r[0]; + $rating_val = $r[0]['xlink_rating']; + $rating_text = $r[0]['xlink_rating_text']; + } + else { + $rating_val = 0; + $rating_text = ''; + } + + // if unset default to enabled + if($poco_rating === false) + $poco_rating = true; + + if($poco_rating) { + $rating = replace_macros(get_markup_template('rating_slider.tpl'),array( + '$min' => -10, + '$val' => $rating_val + )); + } + else { + $rating = false; + } + + $o = replace_macros(get_markup_template('rating_form.tpl'),array( + '$header' => t('Rating'), + '$website' => t('Website:'), + '$site' => ((\App::$data['site']) ? '' . \App::$data['site']['site_url'] . '' : ''), + 'target' => \App::$data['target'], + '$tgt_name' => ((\App::$poi && \App::$poi['xchan_name']) ? \App::$poi['xchan_name'] : sprintf( t('Remote Channel [%s] (not yet known on this site)'), substr(\App::$data['target'],0,16))), + '$lbl_rating' => t('Rating (this information is public)'), + '$lbl_rating_txt' => t('Optionally explain your rating (this information is public)'), + '$rating_txt' => $rating_text, + '$rating' => $rating, + '$rating_val' => $rating_val, + '$slide' => $slide, + '$submit' => t('Submit') + )); + + return $o; + + } +} diff --git a/Zotlabs/Module/Ratings.php b/Zotlabs/Module/Ratings.php new file mode 100644 index 000000000..969fb5015 --- /dev/null +++ b/Zotlabs/Module/Ratings.php @@ -0,0 +1,115 @@ + 1) + $hash = argv(1); + + if(! $hash) { + notice('Must supply a channel identififier.'); + return; + } + + $results = false; + + $x = z_fetch_url($url . '/ratingsearch/' . urlencode($hash)); + + + if($x['success']) + $results = json_decode($x['body'],true); + + + if((! $results) || (! $results['success'])) { + + notice('No results.'); + return; + } + + if(array_key_exists('xchan_hash',$results['target'])) + \App::$poi = $results['target']; + + $friends = array(); + $others = array(); + + if($results['ratings']) { + foreach($results['ratings'] as $n) { + if(is_array(\App::$contacts) && array_key_exists($n['xchan_hash'],\App::$contacts)) + $friends[] = $n; + else + $others[] = $n; + } + } + + \App::$data = array('target' => $results['target'], 'results' => array_merge($friends,$others)); + + if(! \App::$data['results']) { + notice( t('No ratings') . EOL); + } + + return; + } + + + + + + function get() { + + if(observer_prohibited()) { + notice( t('Public access denied.') . EOL); + return; + } + + $poco_rating = get_config('system','poco_rating_enable'); + // if unset default to enabled + if($poco_rating === false) + $poco_rating = true; + + if(! $poco_rating) + return; + + $site_target = ((array_key_exists('target',\App::$data) && array_key_exists('site_url',\App::$data['target'])) ? + '' . \App::$data['target']['site_url'] . '' : ''); + + + $o = replace_macros(get_markup_template('prep.tpl'),array( + '$header' => t('Ratings'), + '$rating_lbl' => t('Rating: ' ), + '$website' => t('Website: '), + '$site' => $site_target, + '$rating_text_lbl' => t('Description: '), + '$raters' => \App::$data['results'] + )); + + return $o; + } + + +} diff --git a/Zotlabs/Module/Ratingsearch.php b/Zotlabs/Module/Ratingsearch.php new file mode 100644 index 000000000..5f463b378 --- /dev/null +++ b/Zotlabs/Module/Ratingsearch.php @@ -0,0 +1,76 @@ + false); + + $dirmode = intval(get_config('system','directory_mode')); + + if($dirmode == DIRECTORY_MODE_NORMAL) { + $ret['message'] = 'This site is not a directory server.'; + json_return_and_die($ret); + } + + if(argc() > 1) + $hash = argv(1); + + if(! $hash) { + $ret['message'] = 'No channel identifier'; + json_return_and_die($ret); + } + + if(strpos($hash,'@')) { + $r = q("select * from hubloc where hubloc_addr = '%s' limit 1", + dbesc($hash) + ); + if($r) + $hash = $r[0]['hubloc_hash']; + } + + $p = q("select * from xchan where xchan_hash like '%s'", + dbesc($hash . '%') + ); + + if($p) + $target = $p[0]['xchan_hash']; + else { + $p = q("select * from site where site_url like '%s' and site_type = %d ", + dbesc('%' . $hash), + intval(SITE_TYPE_ZOT) + ); + if($p) { + $target = strtolower($hash); + } + else { + $ret['message'] = 'Rating target not found'; + json_return_and_die($ret); + } + } + + if($p) + $ret['target'] = $p[0]; + + $ret['success'] = true; + + $r = q("select * from xlink left join xchan on xlink_xchan = xchan_hash + where xlink_link = '%s' and xlink_rating != 0 and xlink_static = 1 order by xchan_name asc", + dbesc($target) + ); + + if($r) { + $ret['ratings'] = $r; + } + else + $ret['ratings'] = array(); + + json_return_and_die($ret); + + } + + +} diff --git a/Zotlabs/Module/Rbmark.php b/Zotlabs/Module/Rbmark.php new file mode 100644 index 000000000..226cef69e --- /dev/null +++ b/Zotlabs/Module/Rbmark.php @@ -0,0 +1,121 @@ + escape_tags($_REQUEST['url']),'term' => escape_tags($_REQUEST['title'])); + bookmark_add($channel,$channel,$t,((x($_REQUEST,'private')) ? intval($_REQUEST['private']) : 0), + array('menu_id' => ((x($_REQUEST,'menu_id')) ? intval($_REQUEST['menu_id']) : 0), + 'menu_name' => ((x($_REQUEST,'menu_name')) ? escape_tags($_REQUEST['menu_name']) : ''), + 'ischat' => ((x($_REQUEST['ischat'])) ? intval($_REQUEST['ischat']) : 0) + )); + + goaway(z_root() . '/bookmarks'); + + } + + + function get() { + + $o = ''; + + if(! local_channel()) { + + // The login procedure is going to bugger our $_REQUEST variables + // so save them in the session. + + if(array_key_exists('url',$_REQUEST)) { + $_SESSION['bookmark'] = $_REQUEST; + } + return login(); + } + + // If we have saved rbmark session variables, but nothing in the current $_REQUEST, recover the saved variables + + if((! array_key_exists('url',$_REQUEST)) && (array_key_exists('bookmark',$_SESSION))) { + $_REQUEST = $_SESSION['bookmark']; + unset($_SESSION['bookmark']); + } + + if($_REQUEST['remote_return']) { + $_SESSION['remote_return'] = $_REQUEST['remote_return']; + } + if(argc() > 1 && argv(1) === 'return') { + if($_SESSION['remote_return']) + goaway($_SESSION['remote_return']); + goaway(z_root() . '/bookmarks'); + } + + $channel = \App::get_channel(); + + + $m = menu_list($channel['channel_id'],'',MENU_BOOKMARK); + + $menus = array(); + if($m) { + $menus = array(0 => ''); + foreach($m as $n) { + $menus[$n['menu_id']] = $n['menu_name']; + } + } + $menu_select = array('menu_id',t('Select a bookmark folder'),false,'',$menus); + + + $o .= replace_macros(get_markup_template('rbmark.tpl'), array( + + '$header' => t('Save Bookmark'), + '$url' => array('url',t('URL of bookmark'),escape_tags($_REQUEST['url'])), + '$title' => array('title',t('Description'),escape_tags($_REQUEST['title'])), + '$ischat' => ((x($_REQUEST,'ischat')) ? intval($_REQUEST['ischat']) : 0), + '$private' => ((x($_REQUEST,'private')) ? intval($_REQUEST['private']) : 0), + '$submit' => t('Save'), + '$menu_name' => array('menu_name',t('Or enter new bookmark folder name'),'',''), + '$menus' => $menu_select + + )); + + + + + + + return $o; + + } + + + +} diff --git a/Zotlabs/Module/React.php b/Zotlabs/Module/React.php new file mode 100644 index 000000000..ed4f87e7e --- /dev/null +++ b/Zotlabs/Module/React.php @@ -0,0 +1,51 @@ + false); + + $url = $_REQUEST['url']; + $access_token = $_REQUEST['t']; + $valid = 0; + + // we probably don't need the realm as we will find out in the probe. + // What we may want to die is throw an error if you're trying to register in a different realm + // so this configuration issue can be discovered. + + $realm = $_REQUEST['realm']; + if(! $realm) + $realm = DIRECTORY_REALM; + + if($realm === DIRECTORY_REALM) { + $valid = 1; + } else { + $token = get_config('system','realm_token'); + if($token && $access_token != $token) { + $result['message'] = 'This realm requires an access token'; + return; + } + $valid = 1; + } + + $dirmode = intval(get_config('system','directory_mode')); + + if ($dirmode == DIRECTORY_MODE_NORMAL) { + $ret['message'] = t('This site is not a directory server'); + json_return_and_die($ret); + } + + $m = null; + if ($url) { + $m = parse_url($url); + + if ((! $m) || ((! @dns_get_record($m['host'], DNS_A + DNS_CNAME + DNS_PTR)) && (! filter_var($m['host'], FILTER_VALIDATE_IP) ))) { + + $result['message'] = 'unparseable url'; + json_return_and_die($result); + } + + $j = \Zotlabs\Zot\Finger::run('[system]@' . $m['host']); + if($j['success'] && $j['guid']) { + $x = import_xchan($j); + if($x['success']) { + $result['success'] = true; + } + } + + if(! $result['success']) + $valid = 0; + + q("update site set site_valid = %d where site_url = '%s' limit 1", + intval($valid), + strtolower($url) + ); + + json_return_and_die($result); + } else { + + // We can put this in the sql without the condition after 31 august 2015 assuming + // most directory servers will have updated by then + // This just makes sure it happens if I forget + + $sql_extra = ((datetime_convert() > datetime_convert('UTC','UTC','2015-08-31')) ? ' and site_valid = 1 ' : '' ); + if ($dirmode == DIRECTORY_MODE_STANDALONE) { + $r = array(array('site_url' => z_root())); + } else { + $r = q("select site_url from site where site_flags in ( 1, 2 ) and site_realm = '%s' and site_type = %d $sql_extra ", + dbesc(get_directory_realm()), + intval(SITE_TYPE_ZOT) + ); + } + if ($r) { + $result['success'] = true; + $result['directories'] = array(); + foreach ($r as $rr) + $result['directories'][] = $rr['site_url']; + + json_return_and_die($result); + } + } + json_return_and_die($result); + } +} diff --git a/Zotlabs/Module/Register.php b/Zotlabs/Module/Register.php new file mode 100644 index 000000000..45123b88d --- /dev/null +++ b/Zotlabs/Module/Register.php @@ -0,0 +1,271 @@ + 1) ? argv(1) : ''); + + // Provide a stored request for somebody desiring a connection + // when they first need to register someplace. Once they've + // created a channel, we'll try to revive the connection request + // and process it. + + if($_REQUEST['connect']) + $_SESSION['connect'] = $_REQUEST['connect']; + + switch($cmd) { + case 'invite_check.json': + $result = check_account_invite($_REQUEST['invite_code']); + break; + case 'email_check.json': + $result = check_account_email($_REQUEST['email']); + break; + case 'password_check.json': + $result = check_account_password($_REQUEST['password']); + break; + default: + break; + } + if($result) { + json_return_and_die($result); + } + } + + + function post() { + + $max_dailies = intval(get_config('system','max_daily_registrations')); + if($max_dailies) { + $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('1 day') + ); + if($r && $r[0]['total'] >= $max_dailies) { + notice( t('Maximum daily site registrations exceeded. Please try again tomorrow.') . EOL); + return; + } + } + + if(! x($_POST,'tos')) { + notice( t('Please indicate acceptance of the Terms of Service. Registration failed.') . EOL); + return; + } + + $policy = get_config('system','register_policy'); + + $email_verify = get_config('system','verify_email'); + + + switch($policy) { + + case REGISTER_OPEN: + $flags = ACCOUNT_OK; + break; + + case REGISTER_APPROVE: + $flags = ACCOUNT_BLOCKED | ACCOUNT_PENDING; + break; + + default: + case REGISTER_CLOSED: + if(! is_site_admin()) { + notice( t('Permission denied.') . EOL ); + return; + } + $flags = ACCOUNT_BLOCKED; + break; + } + + if($email_verify && $policy == REGISTER_OPEN) + $flags = $flags | ACCOUNT_UNVERIFIED; + + + if((! $_POST['password']) || ($_POST['password'] !== $_POST['password2'])) { + notice( t('Passwords do not match.') . EOL); + return; + } + + $arr = $_POST; + $arr['account_flags'] = $flags; + + $result = create_account($arr); + + if(! $result['success']) { + notice($result['message']); + return; + } + require_once('include/security.php'); + + + if($_REQUEST['name']) + set_aconfig($result['account']['account_id'],'register','channel_name',$_REQUEST['name']); + if($_REQUEST['nickname']) + set_aconfig($result['account']['account_id'],'register','channel_address',$_REQUEST['nickname']); + if($_REQUEST['permissions_role']) + set_aconfig($result['account']['account_id'],'register','permissions_role',$_REQUEST['permissions_role']); + + + $using_invites = intval(get_config('system','invitation_only')); + $num_invites = intval(get_config('system','number_invites')); + $invite_code = ((x($_POST,'invite_code')) ? notags(trim($_POST['invite_code'])) : ''); + + if($using_invites && $invite_code) { + q("delete * from register where hash = '%s'", dbesc($invite_code)); + // @FIXME - this also needs to be considered when using 'invites_remaining' in mod/invite.php + set_aconfig($result['account']['account_id'],'system','invites_remaining',$num_invites); + } + + if($policy == REGISTER_OPEN ) { + if($email_verify) { + $res = verify_email_address($result); + } + else { + $res = send_register_success_email($result['email'],$result['password']); + } + if($res) { + info( t('Registration successful. Please check your email for validation instructions.') . EOL ) ; + } + } + elseif($policy == REGISTER_APPROVE) { + $res = send_reg_approval_email($result); + if($res) { + info( t('Your registration is pending approval by the site owner.') . EOL ) ; + } + else { + notice( t('Your registration can not be processed.') . EOL); + } + goaway(z_root()); + } + + if($email_verify) { + goaway(z_root()); + } + + authenticate_success($result['account'],null,true,false,true); + + $new_channel = false; + $next_page = 'new_channel'; + + if(get_config('system','auto_channel_create') || UNO) { + $new_channel = auto_channel_create($result['account']['account_id']); + if($new_channel['success']) { + $channel_id = $new_channel['channel']['channel_id']; + change_channel($channel_id); + $next_page = '~'; + } + else + $new_channel = false; + } + + $x = get_config('system','workflow_register_next'); + if($x) { + $next_page = $x; + $_SESSION['workflow'] = true; + } + + goaway(z_root() . '/' . $next_page); + + } + + + + function get() { + + $registration_is = ''; + $other_sites = ''; + + if(get_config('system','register_policy') == REGISTER_CLOSED) { + if(get_config('system','directory_mode') == DIRECTORY_MODE_STANDALONE) { + notice( t('Registration on this hub is disabled.') . EOL); + return; + } + + $mod = new Pubsites(); + return $mod->get(); + } + + if(get_config('system','register_policy') == REGISTER_APPROVE) { + $registration_is = t('Registration on this hub is by approval only.'); + $other_sites = t('Register at another affiliated hub.'); + } + + $max_dailies = intval(get_config('system','max_daily_registrations')); + if($max_dailies) { + $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", + db_utcnow(), db_quoteinterval('1 day') + ); + if($r && $r[0]['total'] >= $max_dailies) { + logger('max daily registrations exceeded.'); + notice( t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.') . EOL); + return; + } + } + + // Configurable terms of service link + + $tosurl = get_config('system','tos_url'); + if(! $tosurl) + $tosurl = z_root() . '/help/TermsOfService'; + + $toslink = '' . t('Terms of Service') . ''; + + // Configurable whether to restrict age or not - default is based on international legal requirements + // This can be relaxed if you are on a restricted server that does not share with public servers + + if(get_config('system','no_age_restriction')) + $label_tos = sprintf( t('I accept the %s for this website'), $toslink); + else + $label_tos = sprintf( t('I am over 13 years of age and accept the %s for this website'), $toslink); + + $enable_tos = 1 - intval(get_config('system','no_termsofservice')); + + $email = array('email', t('Your email address'), ((x($_REQUEST,'email')) ? strip_tags(trim($_REQUEST['email'])) : "")); + $password = array('password', t('Choose a password'), ((x($_REQUEST,'password')) ? trim($_REQUEST['password']) : "")); + $password2 = array('password2', t('Please re-enter your password'), ((x($_REQUEST,'password2')) ? trim($_REQUEST['password2']) : "")); + $invite_code = array('invite_code', t('Please enter your invitation code'), ((x($_REQUEST,'invite_code')) ? strip_tags(trim($_REQUEST['invite_code'])) : "")); + $name = array('name', t('Name or caption'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), t('Examples: "Bob Jameson", "Lisa and her Horses", "Soccer", "Aviation Group"')); + $nickhub = '@' . str_replace(array('http://','https://','/'), '', get_config('system','baseurl')); + $nickname = array('nickname', t('Choose a short nickname'), ((x($_REQUEST,'nickname')) ? $_REQUEST['nickname'] : ''), sprintf( t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), $nickhub)); + $privacy_role = ((x($_REQUEST,'permissions_role')) ? $_REQUEST['permissions_role'] : ""); + $role = array('permissions_role' , t('Channel role and privacy'), ($privacy_role) ? $privacy_role : 'social', t('Select a channel role with your privacy requirements.') . ' ' . t('Read more about roles') . '',get_roles()); + $tos = array('tos', $label_tos, '', '', array(t('no'),t('yes'))); + + $auto_create = ((UNO) || (get_config('system','auto_channel_create')) ? true : false); + $default_role = ((UNO) ? 'social' : get_config('system','default_permissions_role')); + + require_once('include/bbcode.php'); + + $o = replace_macros(get_markup_template('register.tpl'), array( + + '$title' => t('Registration'), + '$reg_is' => $registration_is, + '$registertext' => bbcode(get_config('system','register_text')), + '$other_sites' => $other_sites, + '$invitations' => get_config('system','invitation_only'), + '$invite_desc' => t('Membership on this site is by invitation only.'), + '$invite_code' => $invite_code, + '$auto_create' => $auto_create, + '$name' => $name, + '$role' => $role, + '$default_role' => $default_role, + '$nickname' => $nickname, + '$enable_tos' => $enable_tos, + '$tos' => $tos, + '$email' => $email, + '$pass1' => $password, + '$pass2' => $password2, + '$submit' => t('Register'), + '$verify_note' => t('This site may require email verification after submitting this form. If you are returned to a login page, please check your email for instructions.') + )); + + return $o; + + } + + +} diff --git a/Zotlabs/Module/Regmod.php b/Zotlabs/Module/Regmod.php new file mode 100644 index 000000000..c7e5c44aa --- /dev/null +++ b/Zotlabs/Module/Regmod.php @@ -0,0 +1,40 @@ +
    ' . login((\App::$config['system']['register_policy'] == REGISTER_CLOSED) ? 0 : 1); + return $o; + } + + if(! is_site_admin()) { + notice( t('Permission denied.') . EOL); + return ''; + } + + if(argc() != 3) + killme(); + + $cmd = argv(1); + $hash = argv(2); + + if($cmd === 'deny') { + if (! account_deny($hash)) killme(); + } + + if($cmd === 'allow') { + if (! account_allow($hash)) killme(); + } + } + +} diff --git a/Zotlabs/Module/Regver.php b/Zotlabs/Module/Regver.php new file mode 100644 index 000000000..82b162f56 --- /dev/null +++ b/Zotlabs/Module/Regver.php @@ -0,0 +1,28 @@ + d1) { + notice( t('Account removals are not allowed within 48 hours of changing the account password.') . EOL); + return; + } + } + + $global_remove = intval($_POST['global']); + + account_remove($account_id, 1 - $global_remove); + } + + function get() { + + if(! local_channel()) + goaway(z_root()); + + $hash = random_string(); + + $_SESSION['remove_account_verify'] = $hash; + $tpl = get_markup_template('removeaccount.tpl'); + $o .= replace_macros($tpl, array( + '$basedir' => z_root(), + '$hash' => $hash, + '$title' => t('Remove This Account'), + '$desc' => array(t('WARNING: '), t('This account and all its channels will be completely removed from the network. '), t('This action is permanent and can not be undone!')), + '$passwd' => t('Please enter your password for verification:'), + '$global' => array('global', t('Remove this account, all its channels and all its channel clones from the network'), false, t('By default only the instances of the channels located on this hub will be removed from the network')), + '$submit' => t('Remove Account') + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Removeme.php b/Zotlabs/Module/Removeme.php new file mode 100644 index 000000000..bc18fe0f8 --- /dev/null +++ b/Zotlabs/Module/Removeme.php @@ -0,0 +1,71 @@ + d1) { + notice( t('Channel removals are not allowed within 48 hours of changing the account password.') . EOL); + return; + } + } + + $global_remove = intval($_POST['global']); + + channel_remove(local_channel(),1 - $global_remove,true); + + } + + + function get() { + + if(! local_channel()) + goaway(z_root()); + + $hash = random_string(); + + $_SESSION['remove_account_verify'] = $hash; + + $tpl = get_markup_template('removeme.tpl'); + $o .= replace_macros($tpl, array( + '$basedir' => z_root(), + '$hash' => $hash, + '$title' => t('Remove This Channel'), + '$desc' => array(t('WARNING: '), t('This channel will be completely removed from the network. '), t('This action is permanent and can not be undone!')), + '$passwd' => t('Please enter your password for verification:'), + '$global' => array('global', t('Remove this channel and all its clones from the network'), false, t('By default only the instance of the channel located on this hub will be removed from the network'), array(t('No'),t('Yes'))), + '$submit' => t('Remove Channel') + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Rmagic.php b/Zotlabs/Module/Rmagic.php new file mode 100644 index 000000000..26b0c46a6 --- /dev/null +++ b/Zotlabs/Module/Rmagic.php @@ -0,0 +1,95 @@ + $address); + call_hooks('reverse_magic_auth', $arr); + + try { + require_once('library/openid/openid.php'); + $openid = new \LightOpenID(z_root()); + $openid->identity = $address; + $openid->returnUrl = z_root() . '/openid'; + $openid->required = array('namePerson/friendly', 'namePerson'); + $openid->optional = array('namePerson/first','media/image/aspect11','media/image/default'); + goaway($openid->authUrl()); + } catch (\Exception $e) { + notice( t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.').'

    '. t('The error message was:').' '.$e->getMessage()); + } + + // if they're still here... + notice( t('Authentication failed.') . EOL); + return; + } + else { + + // Presumed Red identity. Perform reverse magic auth + + if(strpos($address,'@') === false) { + notice('Invalid address.'); + return; + } + + $r = null; + if($address) { + $r = q("select hubloc_url from hubloc where hubloc_addr = '%s' limit 1", + dbesc($address) + ); + } + if($r) { + $url = $r[0]['hubloc_url']; + } + else { + $url = 'https://' . substr($address,strpos($address,'@')+1); + } + + if($url) { + if($_SESSION['return_url']) + $dest = urlencode(z_root() . '/' . str_replace('zid=','zid_=',$_SESSION['return_url'])); + else + $dest = urlencode(z_root() . '/' . str_replace('zid=','zid_=',\App::$query_string)); + + goaway($url . '/magic' . '?f=&dest=' . $dest); + } + } + } + + + function get() { + + $o = replace_macros(get_markup_template('rmagic.tpl'),array( + '$title' => t('Remote Authentication'), + '$desc' => t('Enter your channel address (e.g. channel@example.com)'), + '$submit' => t('Authenticate') + )); + return $o; + + } +} diff --git a/Zotlabs/Module/Rpost.php b/Zotlabs/Module/Rpost.php new file mode 100644 index 000000000..9e3043d10 --- /dev/null +++ b/Zotlabs/Module/Rpost.php @@ -0,0 +1,144 @@ + $arg) { + $url .= '&' . $key . '=' . $arg; + } + goaway($url); + } + } + + // The login procedure is going to bugger our $_REQUEST variables + // so save them in the session. + + if(array_key_exists('body',$_REQUEST)) { + $_SESSION['rpost'] = $_REQUEST; + } + return login(); + } + + // If we have saved rpost session variables, but nothing in the current $_REQUEST, recover the saved variables + + if((! array_key_exists('body',$_REQUEST)) && (array_key_exists('rpost',$_SESSION))) { + $_REQUEST = $_SESSION['rpost']; + unset($_SESSION['rpost']); + } + + if(array_key_exists('channel',$_REQUEST)) { + $r = q("select channel_id from channel where channel_account_id = %d and channel_address = '%s' limit 1", + intval(get_account_id()), + dbesc($_REQUEST['channel']) + ); + if($r) { + require_once('include/security.php'); + $change = change_channel($r[0]['channel_id']); + } + } + + if($_REQUEST['remote_return']) { + $_SESSION['remote_return'] = $_REQUEST['remote_return']; + } + if(argc() > 1 && argv(1) === 'return') { + if($_SESSION['remote_return']) + goaway($_SESSION['remote_return']); + goaway(z_root() . '/network'); + } + + $plaintext = true; + // if(feature_enabled(local_channel(),'richtext')) + // $plaintext = false; + + if(array_key_exists('type', $_REQUEST) && $_REQUEST['type'] === 'html') { + require_once('include/html2bbcode.php'); + $_REQUEST['body'] = html2bbcode($_REQUEST['body']); + } + + $channel = \App::get_channel(); + + + $acl = new \Zotlabs\Access\AccessList($channel); + + $channel_acl = $acl->get(); + + if($_REQUEST['url']) { + $x = z_fetch_url(z_root() . '/linkinfo?f=&url=' . urlencode($_REQUEST['url'])); + if($x['success']) + $_REQUEST['body'] = $_REQUEST['body'] . $x['body']; + } + + $x = array( + 'is_owner' => true, + 'allow_location' => ((intval(get_pconfig($channel['channel_id'],'system','use_browser_location'))) ? '1' : ''), + 'default_location' => $channel['channel_location'], + 'nickname' => $channel['channel_address'], + 'lockstate' => (($acl->is_private()) ? 'lock' : 'unlock'), + 'acl' => populate_acl($channel_acl, true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'), + 'bang' => '', + 'visitor' => true, + 'profile_uid' => local_channel(), + 'title' => $_REQUEST['title'], + 'body' => $_REQUEST['body'], + 'attachment' => $_REQUEST['attachment'], + 'source' => ((x($_REQUEST,'source')) ? strip_tags($_REQUEST['source']) : ''), + 'return_path' => 'rpost/return', + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ); + + $editor = status_editor($a,$x); + + $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( + '$title' => t('Edit post'), + '$editor' => $editor + )); + + return $o; + + } + + + +} diff --git a/Zotlabs/Module/Rsd_xml.php b/Zotlabs/Module/Rsd_xml.php new file mode 100644 index 000000000..e5059834b --- /dev/null +++ b/Zotlabs/Module/Rsd_xml.php @@ -0,0 +1,17 @@ + \Zotlabs\Lib\System::get_platform_name(), + '$baseurl' => z_root(), + '$apipath' => z_root() . '/api/' + )); + killme(); + } + +} + diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php new file mode 100644 index 000000000..402a27d40 --- /dev/null +++ b/Zotlabs/Module/Search.php @@ -0,0 +1,228 @@ +' . "\r\n"; + + $o = '
    ' . "\r\n"; + + $o .= '

    ' . t('Search') . '

    '; + + if(x(\App::$data,'search')) + $search = trim(\App::$data['search']); + else + $search = ((x($_GET,'search')) ? trim(rawurldecode($_GET['search'])) : ''); + + $tag = false; + if(x($_GET,'tag')) { + $tag = true; + $search = ((x($_GET,'tag')) ? trim(rawurldecode($_GET['tag'])) : ''); + } + + if((! local_channel()) || (! feature_enabled(local_channel(),'savedsearch'))) + $o .= search($search,'search-box','/search',((local_channel()) ? true : false)); + + if(strpos($search,'#') === 0) { + $tag = true; + $search = substr($search,1); + } + if(strpos($search,'@') === 0) { + $search = substr($search,1); + goaway(z_root() . '/directory' . '?f=1&navsearch=1&search=' . $search); + } + if(strpos($search,'?') === 0) { + $search = substr($search,1); + goaway(z_root() . '/help' . '?f=1&navsearch=1&search=' . $search); + } + + // look for a naked webbie + if(strpos($search,'@') !== false) { + goaway(z_root() . '/directory' . '?f=1&navsearch=1&search=' . $search); + } + + if(! $search) + return $o; + + if($tag) { + $sql_extra = sprintf(" AND `item`.`id` IN (select `oid` from term where otype = %d and ttype in ( %d , %d) and term = '%s') ", + intval(TERM_OBJ_POST), + intval(TERM_HASHTAG), + intval(TERM_COMMUNITYTAG), + dbesc(protect_sprintf($search)) + ); + } + else { + $regstr = db_getfunc('REGEXP'); + $sql_extra = sprintf(" AND `item`.`body` $regstr '%s' ", dbesc(protect_sprintf(preg_quote($search)))); + } + + // Here is the way permissions work in the search module... + // Only public posts can be shown + // OR your own posts if you are a logged in member + // No items will be shown if the member has a blocked profile wall. + + if((! $update) && (! $load)) { + + // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, + // because browser prefetching might change it on us. We have to deliver it with the page. + + $o .= '' . "\r\n"; + $o .= "\r\n"; + + \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + '$baseurl' => z_root(), + '$pgtype' => 'search', + '$uid' => ((\App::$profile['profile_uid']) ? \App::$profile['profile_uid'] : '0'), + '$gid' => '0', + '$cid' => '0', + '$cmin' => '0', + '$cmax' => '0', + '$star' => '0', + '$liked' => '0', + '$conv' => '0', + '$spam' => '0', + '$fh' => '0', + '$nouveau' => '0', + '$wall' => '0', + '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0), + '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$search' => (($tag) ? urlencode('#') : '') . $search, + '$order' => '', + '$file' => '', + '$cats' => '', + '$tags' => '', + '$mid' => '', + '$verb' => '', + '$dend' => '', + '$dbegin' => '' + )); + + + } + + $item_normal = item_normal(); + $pub_sql = public_permissions_sql($observer_hash); + + require_once('include/channel.php'); + + $sys = get_sys_channel(); + + if(($update) && ($load)) { + $itemspage = get_pconfig(local_channel(),'system','itemspage'); + \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + + // in case somebody turned off public access to sys channel content with permissions + + if(! perm_is_allowed($sys['channel_id'],$observer_hash,'view_stream')) + $sys['xchan_hash'] .= 'disabled'; + + if($load) { + $r = null; + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $prefix = 'distinct on (created, mid)'; + $suffix = 'ORDER BY created DESC, mid'; + } else { + $prefix = 'distinct'; + $suffix = 'group by mid ORDER BY created DESC'; + } + if(local_channel()) { + $r = q("SELECT $prefix mid, item.id as item_id, item.* from item + WHERE ((( `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' AND item_private = 0 ) + OR ( `item`.`uid` = %d )) OR item.owner_xchan = '%s' ) + $item_normal + $sql_extra + $suffix $pager_sql ", + intval(local_channel()), + dbesc($sys['xchan_hash']) + ); + } + if($r === null) { + $r = q("SELECT $prefix mid, item.id as item_id, item.* from item + WHERE (((( `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' + AND `item`.`deny_gid` = '' AND item_private = 0 ) + and owner_xchan in ( " . stream_perms_xchans(($observer) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) + $pub_sql ) OR owner_xchan = '%s') + $item_normal + $sql_extra + $suffix $pager_sql", + dbesc($sys['xchan_hash']) + ); + } + } + else { + $r = array(); + } + } + + if($r) { + xchan_query($r); + $items = fetch_post_tags($r,true); + } else { + $items = array(); + } + + + if($format == 'json') { + $result = array(); + require_once('include/conversation.php'); + foreach($items as $item) { + $item['html'] = bbcode($item['body']); + $x = encode_item($item); + $x['html'] = prepare_text($item['body'],$item['mimetype']); + $result[] = $x; + } + json_return_and_die(array('success' => true,'messages' => $result)); + } + + if($tag) + $o .= '

    ' . sprintf( t('Items tagged with: %s'),htmlspecialchars($search, ENT_COMPAT,'UTF-8')) . '

    '; + else + $o .= '

    ' . sprintf( t('Search results for: %s'),htmlspecialchars($search, ENT_COMPAT,'UTF-8')) . '

    '; + + $o .= conversation($a,$items,'search',$update,'client'); + + $o .= '
    '; + + return $o; + } + + +} diff --git a/Zotlabs/Module/Search_ac.php b/Zotlabs/Module/Search_ac.php new file mode 100644 index 000000000..4e936d97b --- /dev/null +++ b/Zotlabs/Module/Search_ac.php @@ -0,0 +1,82 @@ + $g['xchan_photo_s'], + "name" => '@'.$g['xchan_name'], + "id" => $g['abook_id'], + "link" => $g['xchan_url'], + "label" => '', + "nick" => '', + ); + } + } + + $r = q("select distinct term, tid, url from term where ttype in ( %d, %d ) $tag_sql_extra group by term order by term asc", + intval(TERM_HASHTAG), + intval(TERM_COMMUNITYTAG) + ); + + if(count($r)) { + foreach($r as $g) { + $results[] = array( + "photo" => z_root() . '/images/hashtag.png', + "name" => '#'.$g['term'], + "id" => $g['tid'], + "link" => $g['url'], + "label" => '', + "nick" => '', + ); + } + } + + header("content-type: application/json"); + $o = array( + 'start' => $start, + 'count' => $count, + 'items' => $results, + ); + echo json_encode($o); + + logger('search_ac: ' . print_r($x,true)); + + killme(); + } + + + +} diff --git a/Zotlabs/Module/Service_limits.php b/Zotlabs/Module/Service_limits.php new file mode 100644 index 000000000..2a1f78054 --- /dev/null +++ b/Zotlabs/Module/Service_limits.php @@ -0,0 +1,28 @@ + 1) && (argv(1) === 'oauth') && x($_POST,'remove')){ + check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); + + $key = $_POST['remove']; + q("DELETE FROM tokens WHERE id='%s' AND uid=%d", + dbesc($key), + local_channel()); + goaway(z_root()."/settings/oauth/"); + return; + } + + if((argc() > 2) && (argv(1) === 'oauth') && (argv(2) === 'edit'||(argv(2) === 'add')) && x($_POST,'submit')) { + + check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); + + $name = ((x($_POST,'name')) ? $_POST['name'] : ''); + $key = ((x($_POST,'key')) ? $_POST['key'] : ''); + $secret = ((x($_POST,'secret')) ? $_POST['secret'] : ''); + $redirect = ((x($_POST,'redirect')) ? $_POST['redirect'] : ''); + $icon = ((x($_POST,'icon')) ? $_POST['icon'] : ''); + $ok = true; + if($name == '') { + $ok = false; + notice( t('Name is required') . EOL); + } + if($key == '' || $secret == '') { + $ok = false; + notice( t('Key and Secret are required') . EOL); + } + + if($ok) { + if ($_POST['submit']==t("Update")){ + $r = q("UPDATE clients SET + client_id='%s', + pw='%s', + clname='%s', + redirect_uri='%s', + icon='%s', + uid=%d + WHERE client_id='%s'", + dbesc($key), + dbesc($secret), + dbesc($name), + dbesc($redirect), + dbesc($icon), + intval(local_channel()), + dbesc($key)); + } else { + $r = q("INSERT INTO clients (client_id, pw, clname, redirect_uri, icon, uid) + VALUES ('%s','%s','%s','%s','%s',%d)", + dbesc($key), + dbesc($secret), + dbesc($name), + dbesc($redirect), + dbesc($icon), + intval(local_channel()) + ); + $r = q("INSERT INTO xperm (xp_client, xp_channel, xp_perm) VALUES ('%s', %d, '%s') ", + dbesc($key), + intval(local_channel()), + dbesc('all') + ); + } + } + goaway(z_root()."/settings/oauth/"); + return; + } + + if((argc() > 1) && (argv(1) == 'featured')) { + check_form_security_token_redirectOnErr('/settings/featured', 'settings_featured'); + + call_hooks('feature_settings_post', $_POST); + + build_sync_packet(); + return; + } + + + if((argc() > 1) && (argv(1) == 'tokens')) { + check_form_security_token_redirectOnErr('/settings/tokens', 'settings_tokens'); + $token_errs = 0; + if(array_key_exists('token',$_POST)) { + $atoken_id = (($_POST['atoken_id']) ? intval($_POST['atoken_id']) : 0); + $name = trim(escape_tags($_POST['name'])); + $token = trim($_POST['token']); + if((! $name) || (! $token)) + $token_errs ++; + if(trim($_POST['expires'])) + $expires = datetime_convert(date_default_timezone_get(),'UTC',$_POST['expires']); + else + $expires = NULL_DATE; + $max_atokens = service_class_fetch(local_channel(),'access_tokens'); + if($max_atokens) { + $r = q("select count(atoken_id) as total where atoken_uid = %d", + intval(local_channel()) + ); + if($r && intval($r[0]['total']) >= $max_tokens) { + notice( sprintf( t('This channel is limited to %d tokens'), $max_tokens) . EOL); + return; + } + } + } + if($token_errs) { + notice( t('Name and Password are required.') . EOL); + return; + } + if($atoken_id) { + $r = q("update atoken set atoken_name = '%s', atoken_token = '%s' atoken_expires = '%s' + where atoken_id = %d and atoken_uid = %d", + dbesc($name), + dbesc($token), + dbesc($expires), + intval($atoken_id), + intval($channel['channel_id']) + ); + } + else { + $r = q("insert into atoken ( atoken_aid, atoken_uid, atoken_name, atoken_token, atoken_expires ) + values ( %d, %d, '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc($name), + dbesc($token), + dbesc($expires) + ); + } + + info( t('Token saved.') . EOL); + return; + } + + + + if((argc() > 1) && (argv(1) === 'features')) { + check_form_security_token_redirectOnErr('/settings/features', 'settings_features'); + + // Build list of features and check which are set + $features = get_features(); + $all_features = array(); + foreach($features as $k => $v) { + foreach($v as $f) + $all_features[] = $f[0]; + } + foreach($all_features as $k) { + if(x($_POST,"feature_$k")) + set_pconfig(local_channel(),'feature',$k, 1); + else + set_pconfig(local_channel(),'feature',$k, 0); + } + build_sync_packet(); + return; + } + + if((argc() > 1) && (argv(1) == 'display')) { + + check_form_security_token_redirectOnErr('/settings/display', 'settings_display'); + + $theme = ((x($_POST,'theme')) ? notags(trim($_POST['theme'])) : \App::$channel['channel_theme']); + $mobile_theme = ((x($_POST,'mobile_theme')) ? notags(trim($_POST['mobile_theme'])) : ''); + $preload_images = ((x($_POST,'preload_images')) ? intval($_POST['preload_images']) : 0); + $user_scalable = ((x($_POST,'user_scalable')) ? intval($_POST['user_scalable']) : 0); + $nosmile = ((x($_POST,'nosmile')) ? intval($_POST['nosmile']) : 0); + $title_tosource = ((x($_POST,'title_tosource')) ? intval($_POST['title_tosource']) : 0); + $channel_list_mode = ((x($_POST,'channel_list_mode')) ? intval($_POST['channel_list_mode']) : 0); + $network_list_mode = ((x($_POST,'network_list_mode')) ? intval($_POST['network_list_mode']) : 0); + + $channel_divmore_height = ((x($_POST,'channel_divmore_height')) ? intval($_POST['channel_divmore_height']) : 400); + if($channel_divmore_height < 50) + $channel_divmore_height = 50; + $network_divmore_height = ((x($_POST,'network_divmore_height')) ? intval($_POST['network_divmore_height']) : 400); + if($network_divmore_height < 50) + $network_divmore_height = 50; + + $browser_update = ((x($_POST,'browser_update')) ? intval($_POST['browser_update']) : 0); + $browser_update = $browser_update * 1000; + if($browser_update < 10000) + $browser_update = 10000; + + $itemspage = ((x($_POST,'itemspage')) ? intval($_POST['itemspage']) : 20); + if($itemspage > 100) + $itemspage = 100; + + + if ($mobile_theme == "---") + del_pconfig(local_channel(),'system','mobile_theme'); + else { + set_pconfig(local_channel(),'system','mobile_theme',$mobile_theme); + } + + set_pconfig(local_channel(),'system','preload_images',$preload_images); + set_pconfig(local_channel(),'system','user_scalable',$user_scalable); + set_pconfig(local_channel(),'system','update_interval', $browser_update); + set_pconfig(local_channel(),'system','itemspage', $itemspage); + set_pconfig(local_channel(),'system','no_smilies',1-intval($nosmile)); + set_pconfig(local_channel(),'system','title_tosource',$title_tosource); + set_pconfig(local_channel(),'system','channel_list_mode', $channel_list_mode); + set_pconfig(local_channel(),'system','network_list_mode', $network_list_mode); + set_pconfig(local_channel(),'system','channel_divmore_height', $channel_divmore_height); + set_pconfig(local_channel(),'system','network_divmore_height', $network_divmore_height); + + if ($theme == \App::$channel['channel_theme']){ + // call theme_post only if theme has not been changed + if( ($themeconfigfile = $this->get_theme_config_file($theme)) != null){ + require_once($themeconfigfile); + theme_post($a); + } + } + + $r = q("UPDATE channel SET channel_theme = '%s' WHERE channel_id = %d", + dbesc($theme), + intval(local_channel()) + ); + + call_hooks('display_settings_post', $_POST); + build_sync_packet(); + goaway(z_root() . '/settings/display' ); + return; // NOTREACHED + } + + + if(argc() > 1 && argv(1) === 'account') { + + check_form_security_token_redirectOnErr('/settings/account', 'settings_account'); + + call_hooks('account_settings_post', $_POST); + // call_hooks('settings_account', $_POST); + + $errs = array(); + + $email = ((x($_POST,'email')) ? trim(notags($_POST['email'])) : ''); + $account = \App::get_account(); + if($email != $account['account_email']) { + if(! valid_email($email)) + $errs[] = t('Not valid email.'); + $adm = trim(get_config('system','admin_email')); + if(($adm) && (strcasecmp($email,$adm) == 0)) { + $errs[] = t('Protected email address. Cannot change to that email.'); + $email = \App::$user['email']; + } + if(! $errs) { + $r = q("update account set account_email = '%s' where account_id = %d", + dbesc($email), + intval($account['account_id']) + ); + if(! $r) + $errs[] = t('System failure storing new email. Please try again.'); + } + } + + if($errs) { + foreach($errs as $err) + notice($err . EOL); + $errs = array(); + } + + + if((x($_POST,'npassword')) || (x($_POST,'confirm'))) { + + $origpass = trim($_POST['origpass']); + + require_once('include/auth.php'); + if(! account_verify_password($email,$origpass)) { + $errs[] = t('Password verification failed.'); + } + + $newpass = trim($_POST['npassword']); + $confirm = trim($_POST['confirm']); + + if($newpass != $confirm ) { + $errs[] = t('Passwords do not match. Password unchanged.'); + } + + if((! x($newpass)) || (! x($confirm))) { + $errs[] = t('Empty passwords are not allowed. Password unchanged.'); + } + + if(! $errs) { + $salt = random_string(32); + $password_encoded = hash('whirlpool', $salt . $newpass); + $r = q("update account set account_salt = '%s', account_password = '%s', account_password_changed = '%s' + where account_id = %d", + dbesc($salt), + dbesc($password_encoded), + dbesc(datetime_convert()), + intval(get_account_id()) + ); + if($r) + info( t('Password changed.') . EOL); + else + $errs[] = t('Password update failed. Please try again.'); + } + } + + + if($errs) { + foreach($errs as $err) + notice($err . EOL); + } + goaway(z_root() . '/settings/account' ); + } + + + check_form_security_token_redirectOnErr('/settings', 'settings'); + + call_hooks('settings_post', $_POST); + + $set_perms = ''; + + $role = ((x($_POST,'permissions_role')) ? notags(trim($_POST['permissions_role'])) : ''); + $oldrole = get_pconfig(local_channel(),'system','permissions_role'); + + if(($role != $oldrole) || ($role === 'custom')) { + + if($role === 'custom') { + $hide_presence = (((x($_POST,'hide_presence')) && (intval($_POST['hide_presence']) == 1)) ? 1: 0); + $publish = (((x($_POST,'profile_in_directory')) && (intval($_POST['profile_in_directory']) == 1)) ? 1: 0); + $def_group = ((x($_POST,'group-selection')) ? notags(trim($_POST['group-selection'])) : ''); + $r = q("update channel set channel_default_group = '%s' where channel_id = %d", + dbesc($def_group), + intval(local_channel()) + ); + + $global_perms = \Zotlabs\Access\Permissions::Perms(); + + foreach($global_perms as $k => $v) { + \Zotlabs\Access\PermissionLimits::Set(local_channel(),$k,intval($_POST[$k])); + } + $acl = new \Zotlabs\Access\AccessList($channel); + $acl->set_from_array($_POST); + $x = $acl->get(); + + $r = q("update channel set channel_allow_cid = '%s', channel_allow_gid = '%s', + channel_deny_cid = '%s', channel_deny_gid = '%s' where channel_id = %d", + dbesc($x['allow_cid']), + dbesc($x['allow_gid']), + dbesc($x['deny_cid']), + dbesc($x['deny_gid']), + intval(local_channel()) + ); + } + else { + $role_permissions = \Zotlabs\Access\PermissionRoles::role_perms($_POST['permissions_role']); + if(! $role_permissions) { + notice('Permissions category could not be found.'); + return; + } + $hide_presence = 1 - (intval($role_permissions['online'])); + if($role_permissions['default_collection']) { + $r = q("select hash from groups where uid = %d and gname = '%s' limit 1", + intval(local_channel()), + dbesc( t('Friends') ) + ); + if(! $r) { + require_once('include/group.php'); + group_add(local_channel(), t('Friends')); + group_add_member(local_channel(),t('Friends'),$channel['channel_hash']); + $r = q("select hash from groups where uid = %d and gname = '%s' limit 1", + intval(local_channel()), + dbesc( t('Friends') ) + ); + } + if($r) { + q("update channel set channel_default_group = '%s', channel_allow_gid = '%s', channel_allow_cid = '', channel_deny_gid = '', channel_deny_cid = '' where channel_id = %d", + dbesc($r[0]['hash']), + dbesc('<' . $r[0]['hash'] . '>'), + intval(local_channel()) + ); + } + else { + notice( sprintf('Default privacy group \'%s\' not found. Please create and re-submit permission change.', t('Friends')) . EOL); + return; + } + } + // no default collection + else { + q("update channel set channel_default_group = '', channel_allow_gid = '', channel_allow_cid = '', channel_deny_gid = '', + channel_deny_cid = '' where channel_id = %d", + intval(local_channel()) + ); + } + + $x = \Zotlabs\Access\Permissions::FilledPerms($role_permissions['perms_connect']); + foreach($x as $k => $v) { + set_abconfig(local_channel(),$channel['channel_hash'],'my_perms',$k, $v); + if($role_permissions['perms_auto']) { + set_pconfig(local_channel(),'autoperms',$k,$v); + } + else { + del_pconfig(local_channel(),'autoperms',$k); + } + } + + if($role_permissions['limits']) { + foreach($role_permissions['limits'] as $k => $v) { + \Zotlabs\Access\PermissionLimits::Set(local_channel(),$k,$v); + } + } + if(array_key_exists('directory_publish',$role_permissions)) { + $publish = intval($role_permissions['directory_publish']); + } + } + + set_pconfig(local_channel(),'system','hide_online_status',$hide_presence); + set_pconfig(local_channel(),'system','permissions_role',$role); + } + + $username = ((x($_POST,'username')) ? notags(trim($_POST['username'])) : ''); + $timezone = ((x($_POST,'timezone_select')) ? notags(trim($_POST['timezone_select'])) : ''); + $defloc = ((x($_POST,'defloc')) ? notags(trim($_POST['defloc'])) : ''); + $openid = ((x($_POST,'openid_url')) ? notags(trim($_POST['openid_url'])) : ''); + $maxreq = ((x($_POST,'maxreq')) ? intval($_POST['maxreq']) : 0); + $expire = ((x($_POST,'expire')) ? intval($_POST['expire']) : 0); + $evdays = ((x($_POST,'evdays')) ? intval($_POST['evdays']) : 3); + $photo_path = ((x($_POST,'photo_path')) ? escape_tags(trim($_POST['photo_path'])) : ''); + $attach_path = ((x($_POST,'attach_path')) ? escape_tags(trim($_POST['attach_path'])) : ''); + + $channel_menu = ((x($_POST['channel_menu'])) ? htmlspecialchars_decode(trim($_POST['channel_menu']),ENT_QUOTES) : ''); + + $expire_items = ((x($_POST,'expire_items')) ? intval($_POST['expire_items']) : 0); + $expire_starred = ((x($_POST,'expire_starred')) ? intval($_POST['expire_starred']) : 0); + $expire_photos = ((x($_POST,'expire_photos'))? intval($_POST['expire_photos']) : 0); + $expire_network_only = ((x($_POST,'expire_network_only'))? intval($_POST['expire_network_only']) : 0); + + $allow_location = (((x($_POST,'allow_location')) && (intval($_POST['allow_location']) == 1)) ? 1: 0); + + $blocktags = (((x($_POST,'blocktags')) && (intval($_POST['blocktags']) == 1)) ? 0: 1); // this setting is inverted! + $unkmail = (((x($_POST,'unkmail')) && (intval($_POST['unkmail']) == 1)) ? 1: 0); + $cntunkmail = ((x($_POST,'cntunkmail')) ? intval($_POST['cntunkmail']) : 0); + $suggestme = ((x($_POST,'suggestme')) ? intval($_POST['suggestme']) : 0); + + $post_newfriend = (($_POST['post_newfriend'] == 1) ? 1: 0); + $post_joingroup = (($_POST['post_joingroup'] == 1) ? 1: 0); + $post_profilechange = (($_POST['post_profilechange'] == 1) ? 1: 0); + $adult = (($_POST['adult'] == 1) ? 1 : 0); + + $cal_first_day = (((x($_POST,'first_day')) && (intval($_POST['first_day']) == 1)) ? 1: 0); + + $channel = \App::get_channel(); + $pageflags = $channel['channel_pageflags']; + $existing_adult = (($pageflags & PAGE_ADULT) ? 1 : 0); + if($adult != $existing_adult) + $pageflags = ($pageflags ^ PAGE_ADULT); + + + $notify = 0; + + if(x($_POST,'notify1')) + $notify += intval($_POST['notify1']); + if(x($_POST,'notify2')) + $notify += intval($_POST['notify2']); + if(x($_POST,'notify3')) + $notify += intval($_POST['notify3']); + if(x($_POST,'notify4')) + $notify += intval($_POST['notify4']); + if(x($_POST,'notify5')) + $notify += intval($_POST['notify5']); + if(x($_POST,'notify6')) + $notify += intval($_POST['notify6']); + if(x($_POST,'notify7')) + $notify += intval($_POST['notify7']); + if(x($_POST,'notify8')) + $notify += intval($_POST['notify8']); + + + $vnotify = 0; + + if(x($_POST,'vnotify1')) + $vnotify += intval($_POST['vnotify1']); + if(x($_POST,'vnotify2')) + $vnotify += intval($_POST['vnotify2']); + if(x($_POST,'vnotify3')) + $vnotify += intval($_POST['vnotify3']); + if(x($_POST,'vnotify4')) + $vnotify += intval($_POST['vnotify4']); + if(x($_POST,'vnotify5')) + $vnotify += intval($_POST['vnotify5']); + if(x($_POST,'vnotify6')) + $vnotify += intval($_POST['vnotify6']); + if(x($_POST,'vnotify7')) + $vnotify += intval($_POST['vnotify7']); + if(x($_POST,'vnotify8')) + $vnotify += intval($_POST['vnotify8']); + if(x($_POST,'vnotify9')) + $vnotify += intval($_POST['vnotify9']); + if(x($_POST,'vnotify10')) + $vnotify += intval($_POST['vnotify10']); + if(x($_POST,'vnotify11')) + $vnotify += intval($_POST['vnotify11']); + + $always_show_in_notices = x($_POST,'always_show_in_notices') ? 1 : 0; + + $channel = \App::get_channel(); + + $err = ''; + + $name_change = false; + + if($username != $channel['channel_name']) { + $name_change = true; + require_once('include/channel.php'); + $err = validate_channelname($username); + if($err) { + notice($err); + return; + } + } + + if($timezone != $channel['channel_timezone']) { + if(strlen($timezone)) + date_default_timezone_set($timezone); + } + + set_pconfig(local_channel(),'system','use_browser_location',$allow_location); + set_pconfig(local_channel(),'system','suggestme', $suggestme); + set_pconfig(local_channel(),'system','post_newfriend', $post_newfriend); + set_pconfig(local_channel(),'system','post_joingroup', $post_joingroup); + set_pconfig(local_channel(),'system','post_profilechange', $post_profilechange); + set_pconfig(local_channel(),'system','blocktags',$blocktags); + set_pconfig(local_channel(),'system','channel_menu',$channel_menu); + set_pconfig(local_channel(),'system','vnotify',$vnotify); + set_pconfig(local_channel(),'system','always_show_in_notices',$always_show_in_notices); + set_pconfig(local_channel(),'system','evdays',$evdays); + set_pconfig(local_channel(),'system','photo_path',$photo_path); + set_pconfig(local_channel(),'system','attach_path',$attach_path); + set_pconfig(local_channel(),'system','cal_first_day',$cal_first_day); + + $r = q("update channel set channel_name = '%s', channel_pageflags = %d, channel_timezone = '%s', channel_location = '%s', channel_notifyflags = %d, channel_max_anon_mail = %d, channel_max_friend_req = %d, channel_expire_days = %d $set_perms where channel_id = %d", + dbesc($username), + intval($pageflags), + dbesc($timezone), + dbesc($defloc), + intval($notify), + intval($unkmail), + intval($maxreq), + intval($expire), + intval(local_channel()) + ); + if($r) + info( t('Settings updated.') . EOL); + + if(! is_null($publish)) { + $r = q("UPDATE profile SET publish = %d WHERE is_default = 1 AND uid = %d", + intval($publish), + intval(local_channel()) + ); + } + + if($name_change) { + $r = q("update xchan set xchan_name = '%s', xchan_name_date = '%s' where xchan_hash = '%s'", + dbesc($username), + dbesc(datetime_convert()), + dbesc($channel['channel_hash']) + ); + $r = q("update profile set fullname = '%s' where uid = %d and is_default = 1", + dbesc($username), + intval($channel['channel_id']) + ); + } + + \Zotlabs\Daemon\Master::Summon(array('Directory',local_channel())); + + build_sync_packet(); + + + //$_SESSION['theme'] = $theme; + if($email_changed && \App::$config['system']['register_policy'] == REGISTER_VERIFY) { + + // FIXME - set to un-verified, blocked and redirect to logout + // Why? Are we verifying people or email addresses? + + } + + goaway(z_root() . '/settings' ); + return; // NOTREACHED + } + + + + function get() { + + $o = ''; + nav_set_selected('settings'); + + + if((! local_channel()) || ($_SESSION['delegate'])) { + notice( t('Permission denied.') . EOL ); + return login(); + } + + + $channel = \App::get_channel(); + if($channel) + head_set_icon($channel['xchan_photo_s']); + + $yes_no = array(t('No'),t('Yes')); + + if((argc() > 1) && (argv(1) === 'oauth')) { + + if((argc() > 2) && (argv(2) === 'add')) { + $tpl = get_markup_template("settings_oauth_edit.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_oauth"), + '$title' => t('Add application'), + '$submit' => t('Submit'), + '$cancel' => t('Cancel'), + '$name' => array('name', t('Name'), '', t('Name of application')), + '$key' => array('key', t('Consumer Key'), random_string(16), t('Automatically generated - change if desired. Max length 20')), + '$secret' => array('secret', t('Consumer Secret'), random_string(16), t('Automatically generated - change if desired. Max length 20')), + '$redirect' => array('redirect', t('Redirect'), '', t('Redirect URI - leave blank unless your application specifically requires this')), + '$icon' => array('icon', t('Icon url'), '', t('Optional')), + )); + return $o; + } + + if((argc() > 3) && (argv(2) === 'edit')) { + $r = q("SELECT * FROM clients WHERE client_id='%s' AND uid=%d", + dbesc(argv(3)), + local_channel()); + + if (!count($r)){ + notice(t('Application not found.')); + return; + } + $app = $r[0]; + + $tpl = get_markup_template("settings_oauth_edit.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_oauth"), + '$title' => t('Add application'), + '$submit' => t('Update'), + '$cancel' => t('Cancel'), + '$name' => array('name', t('Name'), $app['clname'] , ''), + '$key' => array('key', t('Consumer Key'), $app['client_id'], ''), + '$secret' => array('secret', t('Consumer Secret'), $app['pw'], ''), + '$redirect' => array('redirect', t('Redirect'), $app['redirect_uri'], ''), + '$icon' => array('icon', t('Icon url'), $app['icon'], ''), + )); + return $o; + } + + if((argc() > 3) && (argv(2) === 'delete')) { + check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth', 't'); + + $r = q("DELETE FROM clients WHERE client_id='%s' AND uid=%d", + dbesc(argv(3)), + local_channel()); + goaway(z_root()."/settings/oauth/"); + return; + } + + + $r = q("SELECT clients.*, tokens.id as oauth_token, (clients.uid=%d) AS my + FROM clients + LEFT JOIN tokens ON clients.client_id=tokens.client_id + WHERE clients.uid IN (%d,0)", + local_channel(), + local_channel()); + + + $tpl = get_markup_template("settings_oauth.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_oauth"), + '$baseurl' => z_root(), + '$title' => t('Connected Apps'), + '$add' => t('Add application'), + '$edit' => t('Edit'), + '$delete' => t('Delete'), + '$consumerkey' => t('Client key starts with'), + '$noname' => t('No name'), + '$remove' => t('Remove authorization'), + '$apps' => $r, + )); + return $o; + + } + if((argc() > 1) && (argv(1) === 'featured')) { + $settings_addons = ""; + + $o = ''; + + $r = q("SELECT * FROM `hook` WHERE `hook` = 'feature_settings' "); + if(! $r) + $settings_addons = t('No feature settings configured'); + + call_hooks('feature_settings', $settings_addons); + + $tpl = get_markup_template("settings_addons.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_featured"), + '$title' => t('Feature/Addon Settings'), + '$settings_addons' => $settings_addons + )); + return $o; + } + + + /* + * ACCOUNT SETTINGS + */ + + + if((argc() > 1) && (argv(1) === 'account')) { + $account_settings = ""; + + call_hooks('account_settings', $account_settings); + + $email = \App::$account['account_email']; + + + $tpl = get_markup_template("settings_account.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_account"), + '$title' => t('Account Settings'), + '$origpass' => array('origpass', t('Current Password'), ' ',''), + '$password1'=> array('npassword', t('Enter New Password'), '', ''), + '$password2'=> array('confirm', t('Confirm New Password'), '', t('Leave password fields blank unless changing')), + '$submit' => t('Submit'), + '$email' => array('email', t('Email Address:'), $email, ''), + '$removeme' => t('Remove Account'), + '$removeaccount' => t('Remove this account including all its channels'), + '$account_settings' => $account_settings + )); + return $o; + } + + if((argc() > 1) && (argv(1) === 'tokens')) { + $atoken = null; + if(argc() > 2) { + $id = argv(2); + + $atoken = q("select * from atoken where atoken_id = %d and atoken_uid = %d", + intval($id), + intval(local_channel()) + ); + + if($atoken) + $atoken = $atoken[0]; + + if($atoken && argc() > 3 && argv(3) === 'drop') { + $r = q("delete from atoken where atoken_id = %d", + intval($id) + ); + } + } + $t = q("select * from atoken where atoken_uid = %d", + intval(local_channel()) + ); + + $desc = t('Use this form to create temporary access identifiers to share things with non-members. These identities may be used in Access Control Lists and visitors may login using these credentials to access the private content.'); + + $desc2 = t('You may also provide dropbox style access links to friends and associates by adding the Login Password to any specific site URL as shown. Examples:'); + + $tpl = get_markup_template("settings_tokens.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_tokens"), + '$title' => t('Guest Access Tokens'), + '$desc' => $desc, + '$desc2' => $desc2, + '$tokens' => $t, + '$atoken' => $atoken, + '$url1' => z_root() . '/channel/' . $channel['channel_address'], + '$url2' => z_root() . '/photos/' . $channel['channel_address'], + '$name' => array('name', t('Login Name') . ' *', (($atoken) ? $atoken['atoken_name'] : ''),''), + '$token'=> array('token', t('Login Password') . ' *',(($atoken) ? $atoken['atoken_token'] : autoname(8)), ''), + '$expires'=> array('expires', t('Expires (yyyy-mm-dd)'), (($atoken['atoken_expires'] && $atoken['atoken_expires'] != NULL_DATE) ? datetime_convert('UTC',date_default_timezone_get(),$atoken['atoken_expires']) : ''), ''), + '$submit' => t('Submit') + )); + return $o; + } + + + + + + if((argc() > 1) && (argv(1) === 'features')) { + $arr = array(); + $features = get_features(); + + foreach($features as $fname => $fdata) { + $arr[$fname] = array(); + $arr[$fname][0] = $fdata[0]; + foreach(array_slice($fdata,1) as $f) { + $arr[$fname][1][] = array('feature_' .$f[0],$f[1],((intval(feature_enabled(local_channel(),$f[0]))) ? "1" : ''),$f[2],array(t('Off'),t('On'))); + } + } + + $tpl = get_markup_template("settings_features.tpl"); + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_features"), + '$title' => t('Additional Features'), + '$features' => $arr, + '$submit' => t('Submit'), + )); + + return $o; + } + + + + + + if((argc() > 1) && (argv(1) === 'connectors')) { + + $settings_connectors = ""; + + call_hooks('connector_settings', $settings_connectors); + + $r = null; + + $tpl = get_markup_template("settings_connectors.tpl"); + + $o .= replace_macros($tpl, array( + '$form_security_token' => get_form_security_token("settings_connectors"), + '$title' => t('Connector Settings'), + '$submit' => t('Submit'), + '$settings_connectors' => $settings_connectors + )); + + call_hooks('display_settings', $o); + return $o; + } + + /* + * DISPLAY SETTINGS + */ + + if((argc() > 1) && (argv(1) === 'display')) { + $default_theme = get_config('system','theme'); + if(! $default_theme) + $default_theme = 'default'; + $default_mobile_theme = get_config('system','mobile_theme'); + if(! $mobile_default_theme) + $mobile_default_theme = 'none'; + + $allowed_themes_str = get_config('system','allowed_themes'); + $allowed_themes_raw = explode(',',$allowed_themes_str); + $allowed_themes = array(); + if(count($allowed_themes_raw)) + foreach($allowed_themes_raw as $x) + if(strlen(trim($x)) && is_dir("view/theme/$x")) + $allowed_themes[] = trim($x); + + + $themes = array(); + $files = glob('view/theme/*'); + if($allowed_themes) { + foreach($allowed_themes as $th) { + $f = $th; + $is_experimental = file_exists('view/theme/' . $th . '/experimental'); + $unsupported = file_exists('view/theme/' . $th . '/unsupported'); + $is_mobile = file_exists('view/theme/' . $th . '/mobile'); + $is_library = file_exists('view/theme/'. $th . '/library'); + $mobile_themes["---"] = t("No special theme for mobile devices"); + + if (!$is_experimental or ($is_experimental && (get_config('experimentals','exp_themes')==1 or get_config('experimentals','exp_themes')===false))){ + $theme_name = (($is_experimental) ? sprintf(t('%s - (Experimental)'), $f) : $f); + if (! $is_library) { + if($is_mobile) { + $mobile_themes[$f] = $themes[$f] = $theme_name . ' (' . t('mobile') . ')'; + } + else { + $mobile_themes[$f] = $themes[$f] = $theme_name; + } + } + } + + } + } + $theme_selected = (!x($_SESSION,'theme')? $default_theme : $_SESSION['theme']); + $mobile_theme_selected = (!x($_SESSION,'mobile_theme')? $default_mobile_theme : $_SESSION['mobile_theme']); + + $preload_images = get_pconfig(local_channel(),'system','preload_images'); + $preload_images = (($preload_images===false)? '0': $preload_images); // default if not set: 0 + + $user_scalable = get_pconfig(local_channel(),'system','user_scalable'); + $user_scalable = (($user_scalable===false)? '1': $user_scalable); // default if not set: 1 + + $browser_update = intval(get_pconfig(local_channel(), 'system','update_interval')); + $browser_update = (($browser_update == 0) ? 80 : $browser_update / 1000); // default if not set: 40 seconds + + $itemspage = intval(get_pconfig(local_channel(), 'system','itemspage')); + $itemspage = (($itemspage > 0 && $itemspage < 101) ? $itemspage : 20); // default if not set: 20 items + + $nosmile = get_pconfig(local_channel(),'system','no_smilies'); + $nosmile = (($nosmile===false)? '0': $nosmile); // default if not set: 0 + + $title_tosource = get_pconfig(local_channel(),'system','title_tosource'); + $title_tosource = (($title_tosource===false)? '0': $title_tosource); // default if not set: 0 + + $theme_config = ""; + if( ($themeconfigfile = $this->get_theme_config_file($theme_selected)) != null){ + require_once($themeconfigfile); + $theme_config = theme_content($a); + } + + $tpl = get_markup_template("settings_display.tpl"); + $o = replace_macros($tpl, array( + '$ptitle' => t('Display Settings'), + '$d_tset' => t('Theme Settings'), + '$d_ctset' => t('Custom Theme Settings'), + '$d_cset' => t('Content Settings'), + '$form_security_token' => get_form_security_token("settings_display"), + '$submit' => t('Submit'), + '$baseurl' => z_root(), + '$uid' => local_channel(), + + '$theme' => (($themes) ? array('theme', t('Display Theme:'), $theme_selected, '', $themes, 'preview') : false), + '$mobile_theme' => (($mobile_themes) ? array('mobile_theme', t('Mobile Theme:'), $mobile_theme_selected, '', $mobile_themes, '') : false), + '$preload_images' => array('preload_images', t("Preload images before rendering the page"), $preload_images, t("The subjective page load time will be longer but the page will be ready when displayed"), $yes_no), + '$user_scalable' => array('user_scalable', t("Enable user zoom on mobile devices"), $user_scalable, '', $yes_no), + '$ajaxint' => array('browser_update', t("Update browser every xx seconds"), $browser_update, t('Minimum of 10 seconds, no maximum')), + '$itemspage' => array('itemspage', t("Maximum number of conversations to load at any time:"), $itemspage, t('Maximum of 100 items')), + '$nosmile' => array('nosmile', t("Show emoticons (smilies) as images"), 1-intval($nosmile), '', $yes_no), + '$title_tosource' => array('title_tosource', t("Link post titles to source"), $title_tosource, '', $yes_no), + '$layout_editor' => t('System Page Layout Editor - (advanced)'), + '$theme_config' => $theme_config, + '$expert' => feature_enabled(local_channel(),'expert'), + '$channel_list_mode' => array('channel_list_mode', t('Use blog/list mode on channel page'), get_pconfig(local_channel(),'system','channel_list_mode'), t('(comments displayed separately)'), $yes_no), + '$network_list_mode' => array('network_list_mode', t('Use blog/list mode on grid page'), get_pconfig(local_channel(),'system','network_list_mode'), t('(comments displayed separately)'), $yes_no), + '$channel_divmore_height' => array('channel_divmore_height', t('Channel page max height of content (in pixels)'), ((get_pconfig(local_channel(),'system','channel_divmore_height')) ? get_pconfig(local_channel(),'system','channel_divmore_height') : 400), t('click to expand content exceeding this height')), + '$network_divmore_height' => array('network_divmore_height', t('Grid page max height of content (in pixels)'), ((get_pconfig(local_channel(),'system','network_divmore_height')) ? get_pconfig(local_channel(),'system','network_divmore_height') : 400) , t('click to expand content exceeding this height')), + + + )); + + return $o; + } + + if(argv(1) === 'channel') { + + require_once('include/acl_selectors.php'); + require_once('include/permissions.php'); + + + $p = q("SELECT * FROM `profile` WHERE `is_default` = 1 AND `uid` = %d LIMIT 1", + intval(local_channel()) + ); + if(count($p)) + $profile = $p[0]; + + load_pconfig(local_channel(),'expire'); + + $channel = \App::get_channel(); + + $global_perms = \Zotlabs\Access\Permissions::Perms(); + + $permiss = array(); + + $perm_opts = array( + array( t('Nobody except yourself'), 0), + array( t('Only those you specifically allow'), PERMS_SPECIFIC), + array( t('Approved connections'), PERMS_CONTACTS), + array( t('Any connections'), PERMS_PENDING), + array( t('Anybody on this website'), PERMS_SITE), + array( t('Anybody in this network'), PERMS_NETWORK), + array( t('Anybody authenticated'), PERMS_AUTHED), + array( t('Anybody on the internet'), PERMS_PUBLIC) + ); + + $limits = \Zotlabs\Access\PermissionLimits::Get(local_channel()); + + foreach($global_perms as $k => $perm) { + $options = array(); + foreach($perm_opts as $opt) { + $options[$opt[1]] = $opt[0]; + } + $permiss[] = array($k,$perm,$limits[$k],'',$options); + } + + + //logger('permiss: ' . print_r($permiss,true)); + + + + $username = $channel['channel_name']; + $nickname = $channel['channel_address']; + $timezone = $channel['channel_timezone']; + $notify = $channel['channel_notifyflags']; + $defloc = $channel['channel_location']; + + $maxreq = $channel['channel_max_friend_req']; + $expire = $channel['channel_expire_days']; + $adult_flag = intval($channel['channel_pageflags'] & PAGE_ADULT); + $sys_expire = get_config('system','default_expire_days'); + + // $unkmail = \App::$user['unkmail']; + // $cntunkmail = \App::$user['cntunkmail']; + + $hide_presence = intval(get_pconfig(local_channel(), 'system','hide_online_status')); + + + $expire_items = get_pconfig(local_channel(), 'expire','items'); + $expire_items = (($expire_items===false)? '1' : $expire_items); // default if not set: 1 + + $expire_notes = get_pconfig(local_channel(), 'expire','notes'); + $expire_notes = (($expire_notes===false)? '1' : $expire_notes); // default if not set: 1 + + $expire_starred = get_pconfig(local_channel(), 'expire','starred'); + $expire_starred = (($expire_starred===false)? '1' : $expire_starred); // default if not set: 1 + + $expire_photos = get_pconfig(local_channel(), 'expire','photos'); + $expire_photos = (($expire_photos===false)? '0' : $expire_photos); // default if not set: 0 + + $expire_network_only = get_pconfig(local_channel(), 'expire','network_only'); + $expire_network_only = (($expire_network_only===false)? '0' : $expire_network_only); // default if not set: 0 + + + $suggestme = get_pconfig(local_channel(), 'system','suggestme'); + $suggestme = (($suggestme===false)? '0': $suggestme); // default if not set: 0 + + $post_newfriend = get_pconfig(local_channel(), 'system','post_newfriend'); + $post_newfriend = (($post_newfriend===false)? '0': $post_newfriend); // default if not set: 0 + + $post_joingroup = get_pconfig(local_channel(), 'system','post_joingroup'); + $post_joingroup = (($post_joingroup===false)? '0': $post_joingroup); // default if not set: 0 + + $post_profilechange = get_pconfig(local_channel(), 'system','post_profilechange'); + $post_profilechange = (($post_profilechange===false)? '0': $post_profilechange); // default if not set: 0 + + $blocktags = get_pconfig(local_channel(),'system','blocktags'); + $blocktags = (($blocktags===false) ? '0' : $blocktags); + + $timezone = date_default_timezone_get(); + + $opt_tpl = get_markup_template("field_checkbox.tpl"); + if(get_config('system','publish_all')) { + $profile_in_dir = ''; + } + else { + $profile_in_dir = replace_macros($opt_tpl,array( + '$field' => array('profile_in_directory', t('Publish your default profile in the network directory'), $profile['publish'], '', $yes_no), + )); + } + + $suggestme = replace_macros($opt_tpl,array( + '$field' => array('suggestme', t('Allow us to suggest you as a potential friend to new members?'), $suggestme, '', $yes_no), + + )); + + $subdir = ((strlen(\App::get_path())) ? '
    ' . t('or') . ' ' . z_root() . '/channel/' . $nickname : ''); + + $tpl_addr = get_markup_template("settings_nick_set.tpl"); + + $prof_addr = replace_macros($tpl_addr,array( + '$desc' => t('Your channel address is'), + '$nickname' => $nickname, + '$subdir' => $subdir, + '$basepath' => \App::get_hostname() + )); + + $stpl = get_markup_template('settings.tpl'); + + $acl = new \Zotlabs\Access\AccessList($channel); + $perm_defaults = $acl->get(); + + require_once('include/group.php'); + $group_select = mini_group_select(local_channel(),$channel['channel_default_group']); + + require_once('include/menu.php'); + $m1 = menu_list(local_channel()); + $menu = false; + if($m1) { + $menu = array(); + $current = get_pconfig(local_channel(),'system','channel_menu'); + $menu[] = array('name' => '', 'selected' => ((! $current) ? true : false)); + foreach($m1 as $m) { + $menu[] = array('name' => htmlspecialchars($m['menu_name'],ENT_COMPAT,'UTF-8'), 'selected' => (($m['menu_name'] === $current) ? ' selected="selected" ' : false)); + } + } + + $evdays = get_pconfig(local_channel(),'system','evdays'); + if(! $evdays) + $evdays = 3; + + $permissions_role = get_pconfig(local_channel(),'system','permissions_role'); + if(! $permissions_role) + $permissions_role = 'custom'; + + $permissions_set = (($permissions_role != 'custom') ? true : false); + + $vnotify = get_pconfig(local_channel(),'system','vnotify'); + $always_show_in_notices = get_pconfig(local_channel(),'system','always_show_in_notices'); + if($vnotify === false) + $vnotify = (-1); + + $o .= replace_macros($stpl,array( + '$ptitle' => t('Channel Settings'), + + '$submit' => t('Submit'), + '$baseurl' => z_root(), + '$uid' => local_channel(), + '$form_security_token' => get_form_security_token("settings"), + '$nickname_block' => $prof_addr, + '$h_basic' => t('Basic Settings'), + '$username' => array('username', t('Full Name:'), $username,''), + '$email' => array('email', t('Email Address:'), $email, ''), + '$timezone' => array('timezone_select' , t('Your Timezone:'), $timezone, '', get_timezones()), + '$defloc' => array('defloc', t('Default Post Location:'), $defloc, t('Geographical location to display on your posts')), + '$allowloc' => array('allow_location', t('Use Browser Location:'), ((get_pconfig(local_channel(),'system','use_browser_location')) ? 1 : ''), '', $yes_no), + + '$adult' => array('adult', t('Adult Content'), $adult_flag, t('This channel frequently or regularly publishes adult content. (Please tag any adult material and/or nudity with #NSFW)'), $yes_no), + + '$h_prv' => t('Security and Privacy Settings'), + '$permissions_set' => $permissions_set, + '$server_role' => \Zotlabs\Lib\System::get_server_role(), + '$perms_set_msg' => t('Your permissions are already configured. Click to view/adjust'), + + '$hide_presence' => array('hide_presence', t('Hide my online presence'),$hide_presence, t('Prevents displaying in your profile that you are online'), $yes_no), + + '$lbl_pmacro' => t('Simple Privacy Settings:'), + '$pmacro3' => t('Very Public - extremely permissive (should be used with caution)'), + '$pmacro2' => t('Typical - default public, privacy when desired (similar to social network permissions but with improved privacy)'), + '$pmacro1' => t('Private - default private, never open or public'), + '$pmacro0' => t('Blocked - default blocked to/from everybody'), + '$permiss_arr' => $permiss, + '$blocktags' => array('blocktags',t('Allow others to tag your posts'), 1-$blocktags, t('Often used by the community to retro-actively flag inappropriate content'), $yes_no), + + '$lbl_p2macro' => t('Advanced Privacy Settings'), + + '$expire' => array('expire',t('Expire other channel content after this many days'),$expire, t('0 or blank to use the website limit.') . ' ' . ((intval($sys_expire)) ? sprintf( t('This website expires after %d days.'),intval($sys_expire)) : t('This website does not expire imported content.')) . ' ' . t('The website limit takes precedence if lower than your limit.')), + '$maxreq' => array('maxreq', t('Maximum Friend Requests/Day:'), intval($channel['channel_max_friend_req']) , t('May reduce spam activity')), + '$permissions' => t('Default Post and Publish Permissions'), + '$permdesc' => t("\x28click to open/close\x29"), + '$aclselect' => populate_acl($perm_defaults, false, \Zotlabs\Lib\PermissionDescription::fromDescription(t('Use my default audience setting for the type of object published'))), + '$suggestme' => $suggestme, + '$group_select' => $group_select, + '$role' => array('permissions_role' , t('Channel permissions category:'), $permissions_role, '', get_roles()), + + '$profile_in_dir' => $profile_in_dir, + '$hide_friends' => $hide_friends, + '$hide_wall' => $hide_wall, + '$unkmail' => $unkmail, + '$cntunkmail' => array('cntunkmail', t('Maximum private messages per day from unknown people:'), intval($channel['channel_max_anon_mail']) ,t("Useful to reduce spamming")), + + + '$h_not' => t('Notification Settings'), + '$activity_options' => t('By default post a status message when:'), + '$post_newfriend' => array('post_newfriend', t('accepting a friend request'), $post_newfriend, '', $yes_no), + '$post_joingroup' => array('post_joingroup', t('joining a forum/community'), $post_joingroup, '', $yes_no), + '$post_profilechange' => array('post_profilechange', t('making an interesting profile change'), $post_profilechange, '', $yes_no), + '$lbl_not' => t('Send a notification email when:'), + '$notify1' => array('notify1', t('You receive a connection request'), ($notify & NOTIFY_INTRO), NOTIFY_INTRO, '', $yes_no), + '$notify2' => array('notify2', t('Your connections are confirmed'), ($notify & NOTIFY_CONFIRM), NOTIFY_CONFIRM, '', $yes_no), + '$notify3' => array('notify3', t('Someone writes on your profile wall'), ($notify & NOTIFY_WALL), NOTIFY_WALL, '', $yes_no), + '$notify4' => array('notify4', t('Someone writes a followup comment'), ($notify & NOTIFY_COMMENT), NOTIFY_COMMENT, '', $yes_no), + '$notify5' => array('notify5', t('You receive a private message'), ($notify & NOTIFY_MAIL), NOTIFY_MAIL, '', $yes_no), + '$notify6' => array('notify6', t('You receive a friend suggestion'), ($notify & NOTIFY_SUGGEST), NOTIFY_SUGGEST, '', $yes_no), + '$notify7' => array('notify7', t('You are tagged in a post'), ($notify & NOTIFY_TAGSELF), NOTIFY_TAGSELF, '', $yes_no), + '$notify8' => array('notify8', t('You are poked/prodded/etc. in a post'), ($notify & NOTIFY_POKE), NOTIFY_POKE, '', $yes_no), + + + '$lbl_vnot' => t('Show visual notifications including:'), + + '$vnotify1' => array('vnotify1', t('Unseen grid activity'), ($vnotify & VNOTIFY_NETWORK), VNOTIFY_NETWORK, '', $yes_no), + '$vnotify2' => array('vnotify2', t('Unseen channel activity'), ($vnotify & VNOTIFY_CHANNEL), VNOTIFY_CHANNEL, '', $yes_no), + '$vnotify3' => array('vnotify3', t('Unseen private messages'), ($vnotify & VNOTIFY_MAIL), VNOTIFY_MAIL, t('Recommended'), $yes_no), + '$vnotify4' => array('vnotify4', t('Upcoming events'), ($vnotify & VNOTIFY_EVENT), VNOTIFY_EVENT, '', $yes_no), + '$vnotify5' => array('vnotify5', t('Events today'), ($vnotify & VNOTIFY_EVENTTODAY), VNOTIFY_EVENTTODAY, '', $yes_no), + '$vnotify6' => array('vnotify6', t('Upcoming birthdays'), ($vnotify & VNOTIFY_BIRTHDAY), VNOTIFY_BIRTHDAY, t('Not available in all themes'), $yes_no), + '$vnotify7' => array('vnotify7', t('System (personal) notifications'), ($vnotify & VNOTIFY_SYSTEM), VNOTIFY_SYSTEM, '', $yes_no), + '$vnotify8' => array('vnotify8', t('System info messages'), ($vnotify & VNOTIFY_INFO), VNOTIFY_INFO, t('Recommended'), $yes_no), + '$vnotify9' => array('vnotify9', t('System critical alerts'), ($vnotify & VNOTIFY_ALERT), VNOTIFY_ALERT, t('Recommended'), $yes_no), + '$vnotify10' => array('vnotify10', t('New connections'), ($vnotify & VNOTIFY_INTRO), VNOTIFY_INTRO, t('Recommended'), $yes_no), + '$vnotify11' => array('vnotify11', t('System Registrations'), ($vnotify & VNOTIFY_REGISTER), VNOTIFY_REGISTER, '', $yes_no), + '$always_show_in_notices' => array('always_show_in_notices', t('Also show new wall posts, private messages and connections under Notices'), $always_show_in_notices, 1, '', $yes_no), + + '$evdays' => array('evdays', t('Notify me of events this many days in advance'), $evdays, t('Must be greater than 0')), + + '$h_advn' => t('Advanced Account/Page Type Settings'), + '$h_descadvn' => t('Change the behaviour of this account for special situations'), + '$pagetype' => $pagetype, + '$expert' => feature_enabled(local_channel(),'expert'), + '$hint' => t('Please enable expert mode (in Settings > Additional features) to adjust!'), + '$lbl_misc' => t('Miscellaneous Settings'), + '$photo_path' => array('photo_path', t('Default photo upload folder'), get_pconfig(local_channel(),'system','photo_path'), t('%Y - current year, %m - current month')), + '$attach_path' => array('attach_path', t('Default file upload folder'), get_pconfig(local_channel(),'system','attach_path'), t('%Y - current year, %m - current month')), + '$menus' => $menu, + '$menu_desc' => t('Personal menu to display in your channel pages'), + '$removeme' => t('Remove Channel'), + '$removechannel' => t('Remove this channel.'), + '$firefoxshare' => t('Firefox Share $Projectname provider'), + '$cal_first_day' => array('first_day', t('Start calendar week on monday'), ((get_pconfig(local_channel(),'system','cal_first_day')) ? 1 : ''), '', $yes_no), + )); + + call_hooks('settings_form',$o); + + $o .= '' . "\r\n"; + + return $o; + } + } + + function get_theme_config_file($theme){ + + $base_theme = \App::$theme_info['extends']; + + if (file_exists("view/theme/$theme/php/config.php")){ + return "view/theme/$theme/php/config.php"; + } + if (file_exists("view/theme/$base_theme/php/config.php")){ + return "view/theme/$base_theme/php/config.php"; + } + return null; + } + + +} diff --git a/Zotlabs/Module/Setup.php b/Zotlabs/Module/Setup.php new file mode 100644 index 000000000..802f0c216 --- /dev/null +++ b/Zotlabs/Module/Setup.php @@ -0,0 +1,737 @@ +install_wizard_pass = intval($_POST['pass']); + else + $this->install_wizard_pass = 1; + + } + + /** + * @brief Handle the actions of the different setup steps. + * + */ + + function post() { + + switch($this->install_wizard_pass) { + case 1: + case 2: + return; + // implied break; + case 3: + $urlpath = \App::get_path(); + $dbhost = trim($_POST['dbhost']); + $dbport = intval(trim($_POST['dbport'])); + $dbuser = trim($_POST['dbuser']); + $dbpass = trim($_POST['dbpass']); + $dbdata = trim($_POST['dbdata']); + $dbtype = intval(trim($_POST['dbtype'])); + $phpath = trim($_POST['phpath']); + $adminmail = trim($_POST['adminmail']); + $siteurl = trim($_POST['siteurl']); + $advanced = ((intval($_POST['advanced'])) ? 1 : 0); + + // $siteurl should not have a trailing slash + + $siteurl = rtrim($siteurl,'/'); + + require_once('include/dba/dba_driver.php'); + + $db = \DBA::dba_factory($dbhost, $dbport, $dbuser, $dbpass, $dbdata, $dbtype, true); + + if(! \DBA::$dba->connected) { + echo 'Database Connect failed: ' . DBA::$dba->error; + killme(); + } + return; + // implied break; + case 4: + $urlpath = \App::get_path(); + $dbhost = notags(trim($_POST['dbhost'])); + $dbport = intval(notags(trim($_POST['dbport']))); + $dbuser = notags(trim($_POST['dbuser'])); + $dbpass = notags(trim($_POST['dbpass'])); + $dbdata = notags(trim($_POST['dbdata'])); + $dbtype = intval(notags(trim($_POST['dbtype']))); + $phpath = notags(trim($_POST['phpath'])); + $timezone = notags(trim($_POST['timezone'])); + $adminmail = notags(trim($_POST['adminmail'])); + $siteurl = notags(trim($_POST['siteurl'])); + $advanced = ((intval($_POST['advanced'])) ? 1 : 0); + + if($siteurl != z_root()) { + $test = z_fetch_url($siteurl."/setup/testrewrite"); + if((! $test['success']) || ($test['body'] != 'ok')) { + \App::$data['url_fail'] = true; + \App::$data['url_error'] = $test['error']; + return; + } + } + + if(! \DBA::$dba->connected) { + // connect to db + $db = \DBA::dba_factory($dbhost, $dbport, $dbuser, $dbpass, $dbdata, $dbtype, true); + } + + if(! \DBA::$dba->connected) { + echo 'CRITICAL: DB not connected.'; + killme(); + } + + $tpl = get_intltext_template('htconfig.tpl'); + $txt = replace_macros($tpl,array( + '$dbhost' => $dbhost, + '$dbport' => $dbport, + '$dbuser' => $dbuser, + '$dbpass' => $dbpass, + '$dbdata' => $dbdata, + '$dbtype' => $dbtype, + '$uno' => 1 - $advanced, + '$timezone' => $timezone, + '$siteurl' => $siteurl, + '$site_id' => random_string(), + '$phpath' => $phpath, + '$adminmail' => $adminmail + )); + + $result = file_put_contents('.htconfig.php', $txt); + if(! $result) { + \App::$data['txt'] = $txt; + } + + $errors = $this->load_database($db); + + if($errors) + \App::$data['db_failed'] = $errors; + else + \App::$data['db_installed'] = true; + + return; + // implied break; + default: + break; + } + } + + function get_db_errno() { + if(class_exists('mysqli')) + return mysqli_connect_errno(); + else + return mysql_errno(); + } + + /** + * @brief Get output for the setup page. + * + * Depending on the state we are currently in it returns different content. + * + * @return string parsed HTML output + */ + + function get() { + + $o = ''; + $wizard_status = ''; + $install_title = t('$Projectname Server - Setup'); + + if(x(\App::$data, 'db_conn_failed')) { + $this->install_wizard_pass = 2; + $wizard_status = t('Could not connect to database.'); + } + if(x(\App::$data, 'url_fail')) { + $this->install_wizard_pass = 3; + $wizard_status = t('Could not connect to specified site URL. Possible SSL certificate or DNS issue.'); + if(\App::$data['url_error']) + $wizard_status .= ' ' . \App::$data['url_error']; + } + + if(x(\App::$data, 'db_create_failed')) { + $this->install_wizard_pass = 2; + $wizard_status = t('Could not create table.'); + } + $db_return_text = ''; + if(x(\App::$data, 'db_installed')) { + $txt = '

    '; + $txt .= t('Your site database has been installed.') . EOL; + $db_return_text .= $txt; + } + if(x(\App::$data, 'db_failed')) { + $txt = t('You may need to import the file "install/schema_xxx.sql" manually using a database client.') . EOL; + $txt .= t('Please see the file "install/INSTALL.txt".') . EOL ."


    " ; + $txt .= "
    ".\App::$data['db_failed'] . "
    ". EOL ; + $db_return_text .= $txt; + } + if(\DBA::$dba && \DBA::$dba->connected) { + $r = q("SELECT COUNT(*) as `total` FROM `account`"); + if($r && count($r) && $r[0]['total']) { + $tpl = get_markup_template('install.tpl'); + return replace_macros($tpl, array( + '$title' => $install_title, + '$pass' => '', + '$status' => t('Permission denied.'), + '$text' => '', + )); + } + } + + if(x(\App::$data, 'txt') && strlen(\App::$data['txt'])) { + $db_return_text .= $this->manual_config($a); + } + + if ($db_return_text != "") { + $tpl = get_markup_template('install.tpl'); + return replace_macros($tpl, array( + '$title' => $install_title, + '$pass' => '', + '$text' => $db_return_text . $this->what_next(), + )); + } + + switch ($this->install_wizard_pass){ + case 1: { // System check + + $checks = array(); + + $this->check_funcs($checks); + + $this->check_htconfig($checks); + + $this->check_store($checks); + + $this->check_smarty3($checks); + + $this->check_keys($checks); + + if (x($_POST, 'phpath')) + $phpath = notags(trim($_POST['phpath'])); + + $this->check_php($phpath, $checks); + + $this->check_phpconfig($checks); + + $this->check_htaccess($checks); + + $checkspassed = array_reduce($checks, "self::check_passed", true); + + $tpl = get_markup_template('install_checks.tpl'); + $o .= replace_macros($tpl, array( + '$title' => $install_title, + '$pass' => t('System check'), + '$checks' => $checks, + '$passed' => $checkspassed, + '$see_install' => t('Please see the file "install/INSTALL.txt".'), + '$next' => t('Next'), + '$reload' => t('Check again'), + '$phpath' => $phpath, + '$baseurl' => z_root(), + )); + return $o; + }; break; + + case 2: { // Database config + + $dbhost = ((x($_POST,'dbhost')) ? notags(trim($_POST['dbhost'])) : '127.0.0.1'); + $dbuser = notags(trim($_POST['dbuser'])); + $dbport = intval(notags(trim($_POST['dbport']))); + $dbpass = notags(trim($_POST['dbpass'])); + $dbdata = notags(trim($_POST['dbdata'])); + $dbtype = intval(notags(trim($_POST['dbtype']))); + $phpath = notags(trim($_POST['phpath'])); + $adminmail = notags(trim($_POST['adminmail'])); + $siteurl = notags(trim($_POST['siteurl'])); + + $tpl = get_markup_template('install_db.tpl'); + $o .= replace_macros($tpl, array( + '$title' => $install_title, + '$pass' => t('Database connection'), + '$info_01' => t('In order to install $Projectname we need to know how to connect to your database.'), + '$info_02' => t('Please contact your hosting provider or site administrator if you have questions about these settings.'), + '$info_03' => t('The database you specify below should already exist. If it does not, please create it before continuing.'), + + '$status' => $wizard_status, + + '$dbhost' => array('dbhost', t('Database Server Name'), $dbhost, t('Default is 127.0.0.1')), + '$dbport' => array('dbport', t('Database Port'), $dbport, t('Communication port number - use 0 for default')), + '$dbuser' => array('dbuser', t('Database Login Name'), $dbuser, ''), + '$dbpass' => array('dbpass', t('Database Login Password'), $dbpass, ''), + '$dbdata' => array('dbdata', t('Database Name'), $dbdata, ''), + '$dbtype' => array('dbtype', t('Database Type'), $dbtype, '', array( 0=>'MySQL', 1=>'PostgreSQL' )), + + '$adminmail' => array('adminmail', t('Site administrator email address'), $adminmail, t('Your account email address must match this in order to use the web admin panel.')), + '$siteurl' => array('siteurl', t('Website URL'), z_root(), t('Please use SSL (https) URL if available.')), + '$lbl_10' => t('Please select a default timezone for your website'), + + '$baseurl' => z_root(), + + '$phpath' => $phpath, + + '$submit' => t('Submit'), + )); + return $o; + }; break; + case 3: { // Site settings + require_once('include/datetime.php'); + $dbhost = ((x($_POST,'dbhost')) ? notags(trim($_POST['dbhost'])) : '127.0.0.1'); + $dbport = intval(notags(trim($_POST['dbuser']))); + $dbuser = notags(trim($_POST['dbuser'])); + $dbpass = notags(trim($_POST['dbpass'])); + $dbdata = notags(trim($_POST['dbdata'])); + $dbtype = intval(notags(trim($_POST['dbtype']))); + $phpath = notags(trim($_POST['phpath'])); + + $adminmail = notags(trim($_POST['adminmail'])); + $siteurl = notags(trim($_POST['siteurl'])); + $timezone = ((x($_POST,'timezone')) ? ($_POST['timezone']) : 'America/Los_Angeles'); + + $tpl = get_markup_template('install_settings.tpl'); + $o .= replace_macros($tpl, array( + '$title' => $install_title, + '$pass' => t('Site settings'), + '$status' => $wizard_status, + + '$dbhost' => $dbhost, + '$dbport' => $dbport, + '$dbuser' => $dbuser, + '$dbpass' => $dbpass, + '$dbdata' => $dbdata, + '$phpath' => $phpath, + '$dbtype' => $dbtype, + + '$adminmail' => array('adminmail', t('Site administrator email address'), $adminmail, t('Your account email address must match this in order to use the web admin panel.')), + + '$siteurl' => array('siteurl', t('Website URL'), z_root(), t('Please use SSL (https) URL if available.')), + '$advanced' => array('advanced', t('Enable $Projectname advanced features?'), 1, t('Some advanced features, while useful - may be best suited for technically proficient audiences')), + + '$timezone' => array('timezone', t('Please select a default timezone for your website'), $timezone, '', get_timezones()), + + '$baseurl' => z_root(), + + '$submit' => t('Submit'), + )); + return $o; + }; break; + } + } + + /** + * @brief Add a check result to the array for output. + * + * @param[in,out] array &$checks array passed to template + * @param string $title a title for the check + * @param boolean $status + * @param boolean $required + * @param[optional] string $help optional help string + */ + function check_add(&$checks, $title, $status, $required, $help = '') { + $checks[] = array( + 'title' => $title, + 'status' => $status, + 'required' => $required, + 'help' => $help + ); + } + + /** + * @brief Checks the PHP environment. + * + * @param[in,out] string &$phpath + * @param[out] array &$checks + */ + function check_php(&$phpath, &$checks) { + $help = ''; + + if(version_compare(PHP_VERSION, '5.5') < 0) { + $help .= t('PHP version 5.5 or greater is required.'); + $this->check_add($checks, t('PHP version'), false, false, $help); + } + + if (strlen($phpath)) { + $passed = file_exists($phpath); + } else { + if(is_windows()) + $phpath = trim(shell_exec('where php')); + else + $phpath = trim(shell_exec('which php')); + + $passed = strlen($phpath); + } + + if(!$passed) { + $help .= t('Could not find a command line version of PHP in the web server PATH.'). EOL; + $help .= t('If you don\'t have a command line version of PHP installed on server, you will not be able to run background polling via cron.') . EOL; + $help .= EOL . EOL ; + $tpl = get_markup_template('field_input.tpl'); + $help .= replace_macros($tpl, array( + '$field' => array('phpath', t('PHP executable path'), $phpath, t('Enter full path to php executable. You can leave this blank to continue the installation.')), + )); + $phpath = ''; + } + + $this->check_add($checks, t('Command line PHP').($passed?" ($phpath)":""), $passed, false, $help); + + if($passed) { + $str = autoname(8); + $cmd = "$phpath install/testargs.php $str"; + $result = trim(shell_exec($cmd)); + $passed2 = $result == $str; + $help = ''; + if(!$passed2) { + $help .= t('The command line version of PHP on your system does not have "register_argc_argv" enabled.'). EOL; + $help .= t('This is required for message delivery to work.'); + } + + $this->check_add($checks, t('PHP register_argc_argv'), $passed, true, $help); + } + } + + /** + * @brief Some PHP configuration checks. + * + * @todo Change how we display such informational text. Add more description + * how to change them. + * + * @param[out] array &$checks + */ + function check_phpconfig(&$checks) { + require_once 'include/environment.php'; + + $help = ''; + + $result = getPhpiniUploadLimits(); + $help = sprintf(t('Your max allowed total upload size is set to %s. Maximum size of one file to upload is set to %s. You are allowed to upload up to %d files at once.'), + userReadableSize($result['post_max_size']), + userReadableSize($result['max_upload_filesize']), + $result['max_file_uploads'] + ); + $help .= '
    ' . t('You can adjust these settings in the servers php.ini.'); + + $this->check_add($checks, t('PHP upload limits'), true, false, $help); + } + + /** + * @brief Check if the openssl implementation can generate keys. + * + * @param[out] array $checks + */ + function check_keys(&$checks) { + $help = ''; + $res = false; + + if (function_exists('openssl_pkey_new')) { + $res = openssl_pkey_new(array( + 'digest_alg' => 'sha1', + 'private_key_bits' => 4096, + 'encrypt_key' => false) + ); + } + + // Get private key + + if (! $res) { + $help .= t('Error: the "openssl_pkey_new" function on this system is not able to generate encryption keys'). EOL; + $help .= t('If running under Windows, please see "http://www.php.net/manual/en/openssl.installation.php".'); + } + + $this->check_add($checks, t('Generate encryption keys'), $res, true, $help); + } + + /** + * @brief Check for some PHP functions and modules. + * + * @param[in,out] array &$checks + */ + function check_funcs(&$checks) { + $ck_funcs = array(); + + // add check metadata, the real check is done bit later and return values set + $this->check_add($ck_funcs, t('libCurl PHP module'), true, true); + $this->check_add($ck_funcs, t('GD graphics PHP module'), true, true); + $this->check_add($ck_funcs, t('OpenSSL PHP module'), true, true); + $this->check_add($ck_funcs, t('mysqli or postgres PHP module'), true, true); + $this->check_add($ck_funcs, t('mb_string PHP module'), true, true); + $this->check_add($ck_funcs, t('xml PHP module'), true, true); + + if(function_exists('apache_get_modules')){ + if (! in_array('mod_rewrite', apache_get_modules())) { + $this->check_add($ck_funcs, t('Apache mod_rewrite module'), false, true, t('Error: Apache webserver mod-rewrite module is required but not installed.')); + } else { + $this->check_add($ck_funcs, t('Apache mod_rewrite module'), true, true); + } + } + if((! function_exists('proc_open')) || strstr(ini_get('disable_functions'),'proc_open')) { + $this->check_add($ck_funcs, t('proc_open'), false, true, t('Error: proc_open is required but is either not installed or has been disabled in php.ini')); + } + else { + $this->check_add($ck_funcs, t('proc_open'), true, true); + } + + if(! function_exists('curl_init')) { + $ck_funcs[0]['status'] = false; + $ck_funcs[0]['help'] = t('Error: libCURL PHP module required but not installed.'); + } + if(! function_exists('imagecreatefromjpeg')) { + $ck_funcs[1]['status'] = false; + $ck_funcs[1]['help'] = t('Error: GD graphics PHP module with JPEG support required but not installed.'); + } + if(! function_exists('openssl_public_encrypt')) { + $ck_funcs[2]['status'] = false; + $ck_funcs[2]['help'] = t('Error: openssl PHP module required but not installed.'); + } + if(! function_exists('mysqli_connect') && !function_exists('pg_connect')) { + $ck_funcs[3]['status'] = false; + $ck_funcs[3]['help'] = t('Error: mysqli or postgres PHP module required but neither are installed.'); + } + if(! function_exists('mb_strlen')) { + $ck_funcs[4]['status'] = false; + $ck_funcs[4]['help'] = t('Error: mb_string PHP module required but not installed.'); + } + if(! extension_loaded('xml')) { + $ck_funcs[6]['status'] = false; + $ck_funcs[6]['help'] = t('Error: xml PHP module required for DAV but not installed.'); + } + + $checks = array_merge($checks, $ck_funcs); + } + + /** + * @brief Check for .htconfig requirements. + * + * @param[out] array &$checks + */ + function check_htconfig(&$checks) { + $status = true; + $help = ''; + + if( (file_exists('.htconfig.php') && !is_writable('.htconfig.php')) || + (!file_exists('.htconfig.php') && !is_writable('.')) ) { + $status = false; + $help = t('The web installer needs to be able to create a file called ".htconfig.php" in the top folder of your web server and it is unable to do so.') .EOL; + $help .= t('This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can.').EOL; + $help .= t('At the end of this procedure, we will give you a text to save in a file named .htconfig.php in your Red top folder.').EOL; + $help .= t('You can alternatively skip this procedure and perform a manual installation. Please see the file "install/INSTALL.txt" for instructions.').EOL; + } + + $this->check_add($checks, t('.htconfig.php is writable'), $status, false, $help); + } + + /** + * @brief Checks for our templating engine Smarty3 requirements. + * + * @param[out] array &$checks + */ + function check_smarty3(&$checks) { + $status = true; + $help = ''; + + if(! is_writable(TEMPLATE_BUILD_PATH) ) { + $status = false; + $help = t('Red uses the Smarty3 template engine to render its web views. Smarty3 compiles templates to PHP to speed up rendering.') .EOL; + $help .= sprintf( t('In order to store these compiled templates, the web server needs to have write access to the directory %s under the top level web folder.'), TEMPLATE_BUILD_PATH) . EOL; + $help .= t('Please ensure that the user that your web server runs as (e.g. www-data) has write access to this folder.').EOL; + $help .= sprintf( t('Note: as a security measure, you should give the web server write access to %s only--not the template files (.tpl) that it contains.'), TEMPLATE_BUILD_PATH) . EOL; + } + + $this->check_add($checks, sprintf( t('%s is writable'), TEMPLATE_BUILD_PATH), $status, true, $help); + } + + /** + * @brief Check for store directory. + * + * @param[out] array &$checks + */ + function check_store(&$checks) { + $status = true; + $help = ''; + + @os_mkdir(TEMPLATE_BUILD_PATH, STORAGE_DEFAULT_PERMISSIONS, true); + + if(! is_writable('store')) { + $status = false; + $help = t('This software uses the store directory to save uploaded files. The web server needs to have write access to the store directory under the Red top level folder') . EOL; + $help .= t('Please ensure that the user that your web server runs as (e.g. www-data) has write access to this folder.').EOL; + } + + $this->check_add($checks, t('store is writable'), $status, true, $help); + } + + /** + * @brief Check URL rewrite und SSL certificate. + * + * @param[out] array &$checks + */ + function check_htaccess(&$checks) { + $a = get_app(); + $status = true; + $help = ''; + $ssl_error = false; + + $url = z_root() . '/setup/testrewrite'; + + if (function_exists('curl_init')){ + $test = z_fetch_url($url); + if(! $test['success']) { + if(strstr($url,'https://')) { + $test = z_fetch_url($url,false,0,array('novalidate' => true)); + if($test['success']) { + $ssl_error = true; + } + } + else { + $test = z_fetch_url(str_replace('http://','https://',$url),false,0,array('novalidate' => true)); + if($test['success']) { + $ssl_error = true; + } + } + + if($ssl_error) { + $help = t('SSL certificate cannot be validated. Fix certificate or disable https access to this site.') . EOL; + $help .= t('If you have https access to your website or allow connections to TCP port 443 (the https: port), you MUST use a browser-valid certificate. You MUST NOT use self-signed certificates!') . EOL; + $help .= t('This restriction is incorporated because public posts from you may for example contain references to images on your own hub.') . EOL; + $help .= t('If your certificate is not recognized, members of other sites (who may themselves have valid certificates) will get a warning message on their own site complaining about security issues.') . EOL; + $help .= t('This can cause usability issues elsewhere (not just on your own site) so we must insist on this requirement.') .EOL; + $help .= t('Providers are available that issue free certificates which are browser-valid.'). EOL; + + $help .= t('If you are confident that the certificate is valid and signed by a trusted authority, check to see if you have failed to install an intermediate cert. These are not normally required by browsers, but are required for server-to-server communications.') . EOL; + + + $this->check_add($checks, t('SSL certificate validation'), false, true, $help); + } + } + + if ((! $test['success']) || ($test['body'] != "ok")) { + $status = false; + $help = t('Url rewrite in .htaccess is not working. Check your server configuration.'.'Test: '.var_export($test,true)); + } + + $this->check_add($checks, t('Url rewrite is working'), $status, true, $help); + } else { + // cannot check modrewrite if libcurl is not installed + } + } + + + function manual_config(&$a) { + $data = htmlspecialchars(\App::$data['txt'], ENT_COMPAT, 'UTF-8'); + $o = t('The database configuration file ".htconfig.php" could not be written. Please use the enclosed text to create a configuration file in your web server root.'); + $o .= ""; + + return $o; + } + + function load_database_rem($v, $i){ + $l = trim($i); + if (strlen($l)>1 && ($l[0]=="-" || ($l[0]=="/" && $l[1]=="*"))){ + return $v; + } else { + return $v."\n".$i; + } + } + + + function load_database($db) { + $str = file_get_contents(\DBA::$dba->get_install_script()); + $arr = explode(';',$str); + $errors = false; + foreach($arr as $a) { + if(strlen(trim($a))) { + $r = dbq(trim($a)); + if(! $r) { + $errors .= t('Errors encountered creating database tables.') . $a . EOL; + } + } + } + + return $errors; + } + + function what_next() { + $a = get_app(); + // install the standard theme + set_config('system', 'allowed_themes', 'redbasic'); + + + // Set a lenient list of ciphers if using openssl. Other ssl engines + // (e.g. NSS used in RedHat) require different syntax, so hopefully + // the default curl cipher list will work for most sites. If not, + // this can set via config. Many distros are now disabling RC4, + // but many Red sites still use it and are unable to change it. + // We do not use SSL for encryption, only to protect session cookies. + // z_fetch_url() is also used to import shared links and other content + // so in theory most any cipher could show up and we should do our best + // to make the content available rather than tell folks that there's a + // weird SSL error which they can't do anything about. This does not affect + // the SSL server, but is only a client negotiation to find something workable. + // Hence it will not make your system susceptible to POODL or other nasties. + + $x = curl_version(); + if(stristr($x['ssl_version'],'openssl')) + set_config('system','curl_ssl_ciphers','ALL:!eNULL'); + + // Create a system channel + require_once ('include/channel.php'); + create_sys_channel(); + + $baseurl = z_root(); + return + t('

    What next

    ') + ."

    ".t('IMPORTANT: You will need to [manually] setup a scheduled task for the poller.') + .t('Please see the file "install/INSTALL.txt".') + ."

    " + .t("Go to your new hub registration page and register as new member. Remember to use the same email you have entered as administrator email. This will allow you to enter the site admin panel.") + ."

    "; + } + + + static private function check_passed($v, $c) { + if ($c['required']) + $v = $v && $c['status']; + + return $v; + } + + +} diff --git a/Zotlabs/Module/Share.php b/Zotlabs/Module/Share.php new file mode 100644 index 000000000..fcc2486ba --- /dev/null +++ b/Zotlabs/Module/Share.php @@ -0,0 +1,93 @@ + 1) ? intval(argv(1)) : 0); + + if(! $post_id) + killme(); + + if(! (local_channel() || remote_channel())) + killme(); + + $r = q("SELECT * from item left join xchan on author_xchan = xchan_hash WHERE id = %d LIMIT 1", + intval($post_id) + ); + if(! $r) + killme(); + if(($r[0]['item_private']) && ($r[0]['xchan_network'] !== 'rss')) + killme(); + + $sql_extra = item_permissions_sql($r[0]['uid']); + + $r = q("select * from item where id = %d $sql_extra", + intval($post_id) + ); + if(! $r) + killme(); + + /** @FIXME we only share bbcode */ + + if($r[0]['mimetype'] !== 'text/bbcode') + killme(); + + /** @FIXME eventually we want to post remotely via rpost on your home site */ + // When that works remove this next bit: + + if(! local_channel()) + killme(); + + xchan_query($r); + + $is_photo = (($r[0]['obj_type'] === ACTIVITY_OBJ_PHOTO) ? true : false); + if($is_photo) { + $object = json_decode($r[0]['obj'],true); + $photo_bb = $object['body']; + } + + if (strpos($r[0]['body'], "[/share]") !== false) { + $pos = strpos($r[0]['body'], "[share"); + $o = substr($r[0]['body'], $pos); + } else { + $o = "[share author='".urlencode($r[0]['author']['xchan_name']). + "' profile='".$r[0]['author']['xchan_url'] . + "' avatar='".$r[0]['author']['xchan_photo_s']. + "' link='".$r[0]['plink']. + "' posted='".$r[0]['created']. + "' message_id='".$r[0]['mid']."']"; + if($r[0]['title']) + $o .= '[b]'.$r[0]['title'].'[/b]'."\r\n"; + $o .= (($is_photo) ? $photo_bb . "\r\n" . $r[0]['body'] : $r[0]['body']); + $o .= "[/share]"; + } + + if(local_channel()) { + echo $o; + killme(); + } + + $observer = \App::get_observer(); + $parsed = $observer['xchan_url']; + if($parsed) { + $post_url = $parsed['scheme'] . ':' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : '') + . '/rpost'; + + /** + * @FIXME we were probably called from JS so we don't know the return page. + * In fact we won't be able to load the remote page. + * we might need an iframe + */ + + $x = z_post_url($post_url, array('f' => '', 'body' => $o )); + killme(); + } + } + +} diff --git a/Zotlabs/Module/Sharedwithme.php b/Zotlabs/Module/Sharedwithme.php new file mode 100644 index 000000000..25bc7dba3 --- /dev/null +++ b/Zotlabs/Module/Sharedwithme.php @@ -0,0 +1,113 @@ + 2) && (argv(2) === 'drop')) { + + $id = intval(argv(1)); + + q("DELETE FROM item WHERE id = %d AND uid = %d", + intval($id), + intval(local_channel()) + ); + + goaway(z_root() . '/sharedwithme'); + } + + //drop all files - localuser + if((argc() > 1) && (argv(1) === 'dropall')) { + + q("DELETE FROM item WHERE verb = '%s' AND obj_type = '%s' AND uid = %d", + dbesc(ACTIVITY_POST), + dbesc(ACTIVITY_OBJ_FILE), + intval(local_channel()) + ); + + goaway(z_root() . '/sharedwithme'); + } + + //list files + $r = q("SELECT id, uid, obj, item_unseen FROM item WHERE verb = '%s' AND obj_type = '%s' AND uid = %d AND owner_xchan != '%s'", + dbesc(ACTIVITY_POST), + dbesc(ACTIVITY_OBJ_FILE), + intval(local_channel()), + dbesc($channel['channel_hash']) + ); + + $items =array(); + $ids = ''; + + if($r) { + + foreach($r as $rr) { + $object = json_decode($rr['obj'],true); + + $item = array(); + $item['id'] = $rr['id']; + $item['objfiletype'] = $object['filetype']; + $item['objfiletypeclass'] = getIconFromType($object['filetype']); + $item['objurl'] = rawurldecode(get_rel_link($object['link'],'alternate')) . '?f=&zid=' . $channel['xchan_addr']; + $item['objfilename'] = $object['filename']; + $item['objfilesize'] = userReadableSize($object['filesize']); + $item['objedited'] = $object['edited']; + $item['unseen'] = $rr['item_unseen']; + + $items[] = $item; + + if($item['unseen'] > 0) { + $ids .= " '" . $rr['id'] . "',"; + } + + } + + } + + if($ids) { + + //remove trailing , + $ids = rtrim($ids, ","); + + q("UPDATE item SET item_unseen = 0 WHERE id IN ( $ids ) AND uid = %d", + intval(local_channel()) + ); + + } + + $o = profile_tabs($a, $is_owner, $channel['channel_address']); + + $o .= replace_macros(get_markup_template('sharedwithme.tpl'), array( + '$header' => t('Files: shared with me'), + '$name' => t('Name'), + '$label_new' => t('NEW'), + '$size' => t('Size'), + '$lastmod' => t('Last Modified'), + '$dropall' => t('Remove all files'), + '$drop' => t('Remove this file'), + '$items' => $items + )); + + return $o; + + } + + +} diff --git a/Zotlabs/Module/Siteinfo.php b/Zotlabs/Module/Siteinfo.php new file mode 100644 index 000000000..a15e2896d --- /dev/null +++ b/Zotlabs/Module/Siteinfo.php @@ -0,0 +1,77 @@ + 16) + $commit = ''; + } + else { + $version = $commit = ''; + } + + $plugins_list = implode(', ',visible_plugin_list()); + + if($plugins_list) + $plugins_text = t('Installed plugins/addons/apps:'); + else + $plugins_text = t('No installed plugins/addons/apps'); + + $txt = get_config('system','admininfo'); + $admininfo = bbcode($txt); + + if(file_exists('doc/site_donate.html')) + $donate .= file_get_contents('doc/site_donate.html'); + + if(function_exists('sys_getloadavg')) + $loadavg = sys_getloadavg(); + + $o = replace_macros(get_markup_template('siteinfo.tpl'), array( + '$title' => t('$Projectname'), + '$description' => t('This is a hub of $Projectname - a global cooperative network of decentralized privacy enhanced websites.'), + '$version' => $version, + '$tag_txt' => t('Tag: '), + '$tag' => $tag, + '$polled' => t('Last background fetch: '), + '$lastpoll' => get_poller_runtime(), + '$load_average' => t('Current load average: '), + '$loadavg_all' => $loadavg[0] . ', ' . $loadavg[1] . ', ' . $loadavg[2], + '$commit' => $commit, + '$web_location' => t('Running at web location') . ' ' . z_root(), + '$visit' => t('Please visit hubzilla.org to learn more about $Projectname.'), + '$bug_text' => t('Bug reports and issues: please visit'), + '$bug_link_url' => 'https://github.com/redmatrix/hubzilla/issues', + '$bug_link_text' => t('$projectname issues'), + '$contact' => t('Suggestions, praise, etc. - please email "redmatrix" at librelist - dot com'), + '$donate' => $donate, + '$adminlabel' => t('Site Administrators'), + '$admininfo' => $admininfo, + '$plugins_text' => $plugins_text, + '$plugins_list' => $plugins_list + )); + + call_hooks('about_hook', $o); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Siteinfo_json.php b/Zotlabs/Module/Siteinfo_json.php new file mode 100644 index 000000000..99c22610f --- /dev/null +++ b/Zotlabs/Module/Siteinfo_json.php @@ -0,0 +1,14 @@ + false); + + $r = q("select count(site_url) as total from site where site_type = %d $sql_extra ", + intval(SITE_TYPE_ZOT) + ); + + if($r) + $result['total'] = intval($r[0]['total']); + + $result['start'] = $start; + $result['limit'] = $limit; + + $r = q("select * from site where site_type = %d $sql_extra $sql_order $sql_limit", + intval(SITE_TYPE_ZOT) + ); + + $result['results'] = 0; + $result['entries'] = array(); + + if($r) { + $result['success'] = true; + $result['results'] = count($r); + + foreach($r as $rr) { + $result['entries'][] = array('url' => $rr['site_url']); + } + + } + + echo json_encode($result); + killme(); + + + } +} diff --git a/Zotlabs/Module/Smilies.php b/Zotlabs/Module/Smilies.php new file mode 100644 index 000000000..efac07f84 --- /dev/null +++ b/Zotlabs/Module/Smilies.php @@ -0,0 +1,21 @@ + $tmp['texts'][$i], 'icon' => $tmp['icons'][$i]); + } + json_return_and_die($results); + } + else { + return smilies('',true); + } + } + +} diff --git a/Zotlabs/Module/Sources.php b/Zotlabs/Module/Sources.php new file mode 100644 index 000000000..a180d9b6e --- /dev/null +++ b/Zotlabs/Module/Sources.php @@ -0,0 +1,180 @@ + t('Channel Sources'), + '$desc' => t('Manage remote sources of content for your channel.'), + '$new' => t('New Source'), + '$sources' => $r + )); + return $o; + } + + if(argc() == 2 && argv(1) === 'new') { + // TODO add the words 'or RSS feed' and corresponding code to manage feeds and frequency + + $o = replace_macros(get_markup_template('sources_new.tpl'), array( + '$title' => t('New Source'), + '$desc' => t('Import all or selected content from the following channel into this channel and distribute it according to your channel settings.'), + '$words' => array( 'words', t('Only import content with these words (one per line)'),'',t('Leave blank to import all public content')), + '$name' => array( 'name', t('Channel Name'), '', ''), + '$tags' => array('tags', t('Add the following categories to posts imported from this source (comma separated)'),'',t('Optional')), + + '$submit' => t('Submit') + )); + return $o; + + } + + if(argc() == 2 && intval(argv(1))) { + // edit source + $r = q("select source.*, xchan.* from source left join xchan on src_xchan = xchan_hash where src_id = %d and src_channel_id = %d limit 1", + intval(argv(1)), + intval(local_channel()) + ); + if($r) { + $x = q("select abook_id from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($r[0]['src_xchan']), + intval(local_channel()) + ); + } + if(! $r) { + notice( t('Source not found.') . EOL); + return ''; + } + + $r[0]['src_patt'] = htmlspecialchars($r[0]['src_patt'], ENT_QUOTES,'UTF-8'); + + $o = replace_macros(get_markup_template('sources_edit.tpl'), array( + '$title' => t('Edit Source'), + '$drop' => t('Delete Source'), + '$id' => $r[0]['src_id'], + '$desc' => t('Import all or selected content from the following channel into this channel and distribute it according to your channel settings.'), + '$words' => array( 'words', t('Only import content with these words (one per line)'),$r[0]['src_patt'],t('Leave blank to import all public content')), + '$xchan' => $r[0]['src_xchan'], + '$abook' => $x[0]['abook_id'], + '$tags' => array('tags', t('Add the following categories to posts imported from this source (comma separated)'),$r[0]['src_tag'],t('Optional')), + '$name' => array( 'name', t('Channel Name'), $r[0]['xchan_name'], ''), + '$submit' => t('Submit') + )); + return $o; + + } + + if(argc() == 3 && intval(argv(1)) && argv(2) === 'drop') { + $r = q("select * from source where src_id = %d and src_channel_id = %d limit 1", + intval(argv(1)), + intval(local_channel()) + ); + if(! $r) { + notice( t('Source not found.') . EOL); + return ''; + } + $r = q("delete from source where src_id = %d and src_channel_id = %d", + intval(argv(1)), + intval(local_channel()) + ); + if($r) + info( t('Source removed') . EOL); + else + notice( t('Unable to remove source.') . EOL); + + goaway(z_root() . '/sources'); + + } + + // shouldn't get here. + + } +} diff --git a/Zotlabs/Module/Sslify.php b/Zotlabs/Module/Sslify.php new file mode 100644 index 000000000..db73f85e0 --- /dev/null +++ b/Zotlabs/Module/Sslify.php @@ -0,0 +1,30 @@ + 1) + $message_id = intval(argv(1)); + if(! $message_id) + killme(); + + $r = q("SELECT item_flags FROM item WHERE uid = %d AND id = %d LIMIT 1", + intval(local_channel()), + intval($message_id) + ); + if(! count($r)) + killme(); + + $item_starred = (intval($r[0]['item_starred']) ? 0 : 1); + + $r = q("UPDATE item SET item_starred = %d WHERE uid = %d and id = %d", + intval($item_starred), + intval(local_channel()), + intval($message_id) + ); + + $r = q("select * from item where id = %d", + intval($message_id) + ); + if($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + build_sync_packet(local_channel(),[ + 'item' => [ + encode_item($sync_item[0],true) + ] + ]); + } + + header('Content-type: application/json'); + echo json_encode(array('result' => $item_starred)); + killme(); + } + +} diff --git a/Zotlabs/Module/Subthread.php b/Zotlabs/Module/Subthread.php new file mode 100644 index 000000000..0226664d7 --- /dev/null +++ b/Zotlabs/Module/Subthread.php @@ -0,0 +1,169 @@ + 2) ? notags(trim(argv(2))) : 0); + + if(argv(1) === 'sub') + $activity = ACTIVITY_FOLLOW; + elseif(argv(1) === 'unsub') + $activity = ACTIVITY_UNFOLLOW; + + + $r = q("SELECT parent FROM item WHERE id = '%s'", + dbesc($item_id) + ); + + if($r) { + $r = q("select * from item where id = parent and id = %d limit 1", + dbesc($r[0]['parent']) + ); + } + + if((! $item_id) || (! $r)) { + logger('subthread: no item ' . $item_id); + return; + } + + $item = $r[0]; + + $owner_uid = $item['uid']; + $observer = \App::get_observer(); + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + if(! perm_is_allowed($owner_uid,$ob_hash,'post_comments')) + return; + + $sys = get_sys_channel(); + + $owner_uid = $item['uid']; + $owner_aid = $item['aid']; + + // if this is a "discover" item, (item['uid'] is the sys channel), + // fallback to the item comment policy, which should've been + // respected when generating the conversation thread. + // Even if the activity is rejected by the item owner, it should still get attached + // to the local discover conversation on this site. + + if(($owner_uid != $sys['channel_id']) && (! perm_is_allowed($owner_uid,$observer['xchan_hash'],'post_comments'))) { + notice( t('Permission denied') . EOL); + killme(); + } + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($item['owner_xchan']) + ); + if($r) + $thread_owner = $r[0]; + else + killme(); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($item['author_xchan']) + ); + if($r) + $item_author = $r[0]; + else + killme(); + + + + + $mid = item_message_id(); + + $post_type = (($item['resource_type'] === 'photo') ? t('photo') : t('status')); + + $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $item['plink'])); + $objtype = (($item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); + + $body = $item['body']; + + $obj = json_encode(array( + 'type' => $objtype, + 'id' => $item['mid'], + 'parent' => (($item['thr_parent']) ? $item['thr_parent'] : $item['parent_mid']), + 'link' => $links, + 'title' => $item['title'], + 'content' => $item['body'], + 'created' => $item['created'], + 'edited' => $item['edited'], + 'author' => array( + 'name' => $item_author['xchan_name'], + 'address' => $item_author['xchan_addr'], + 'guid' => $item_author['xchan_guid'], + 'guid_sig' => $item_author['xchan_guid_sig'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']), + array('rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m'])), + ), + )); + + if(! intval($item['item_thread_top'])) + $post_type = 'comment'; + + if($activity === ACTIVITY_FOLLOW) + $bodyverb = t('%1$s is following %2$s\'s %3$s'); + if($activity === ACTIVITY_UNFOLLOW) + $bodyverb = t('%1$s stopped following %2$s\'s %3$s'); + + $arr = array(); + + $arr['mid'] = $mid; + $arr['aid'] = $owner_aid; + $arr['uid'] = $owner_uid; + $arr['parent'] = $item['id']; + $arr['parent_mid'] = $item['mid']; + $arr['thr_parent'] = $item['mid']; + $arr['owner_xchan'] = $thread_owner['xchan_hash']; + $arr['author_xchan'] = $observer['xchan_hash']; + $arr['item_origin'] = 1; + $arr['item_notshown'] = 1; + if(intval($item['item_wall'])) + $arr['item_wall'] = 1; + else + $arr['item_wall'] = 0; + + $ulink = '[zrl=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/zrl]'; + $alink = '[zrl=' . $observer['xchan_url'] . ']' . $observer['xchan_name'] . '[/zrl]'; + $plink = '[zrl=' . z_root() . '/display/' . $item['mid'] . ']' . $post_type . '[/zrl]'; + + $arr['body'] = sprintf( $bodyverb, $alink, $ulink, $plink ); + + $arr['verb'] = $activity; + $arr['obj_type'] = $objtype; + $arr['obj'] = $obj; + + $arr['allow_cid'] = $item['allow_cid']; + $arr['allow_gid'] = $item['allow_gid']; + $arr['deny_cid'] = $item['deny_cid']; + $arr['deny_gid'] = $item['deny_gid']; + + $post = item_store($arr); + $post_id = $post['item_id']; + + $arr['id'] = $post_id; + + call_hooks('post_local_end', $arr); + + killme(); + + + } + + + + +} diff --git a/Zotlabs/Module/Suggest.php b/Zotlabs/Module/Suggest.php new file mode 100644 index 000000000..367308d90 --- /dev/null +++ b/Zotlabs/Module/Suggest.php @@ -0,0 +1,72 @@ + chanlink_url($rr['xchan_url']), + 'common' => $rr['total'], + 'profile' => $rr['xchan_url'], + 'name' => $rr['xchan_name'], + 'photo' => $rr['xchan_photo_m'], + 'ignlnk' => z_root() . '/suggest?ignore=' . $rr['xchan_hash'], + 'conntxt' => t('Connect'), + 'connlnk' => $connlnk, + 'ignore' => t('Ignore/Hide') + ); + } + + + $o = replace_macros(get_markup_template('suggest_page.tpl'),array( + '$title' => t('Channel Suggestions'), + '$entries' => $arr + )); + + return $o; + + } + +} diff --git a/Zotlabs/Module/Tagger.php b/Zotlabs/Module/Tagger.php new file mode 100644 index 000000000..25f518d53 --- /dev/null +++ b/Zotlabs/Module/Tagger.php @@ -0,0 +1,145 @@ + 1) ? notags(trim(argv(1))) : 0); + + logger('tagger: tag ' . $term . ' item ' . $item_id); + + + $r = q("SELECT * FROM item left join xchan on xchan_hash = author_xchan WHERE id = '%s' and uid = %d LIMIT 1", + dbesc($item_id), + intval(local_channel()) + ); + + if((! $item_id) || (! $r)) { + logger('tagger: no item ' . $item_id); + return; + } + + $item = $r[0]; + + $owner_uid = $item['uid']; + + switch($item['resource_type']) { + case 'photo': + $targettype = ACTIVITY_OBJ_PHOTO; + $post_type = t('photo'); + break; + case 'event': + $targgettype = ACTIVITY_OBJ_EVENT; + $post_type = t('event'); + break; + default: + $targettype = ACTIVITY_OBJ_NOTE; + $post_type = t('post'); + if($item['mid'] != $item['parent_mid']) + $post_type = t('comment'); + break; + } + + + $links = array(array('rel' => 'alternate','type' => 'text/html', + 'href' => z_root() . '/display/' . $item['mid'])); + + $target = json_encode(array( + 'type' => $targettype, + 'id' => $item['mid'], + 'link' => $links, + 'title' => $item['title'], + 'content' => $item['body'], + 'created' => $item['created'], + 'edited' => $item['edited'], + 'author' => array( + 'name' => $item['xchan_name'], + 'address' => $item['xchan_addr'], + 'guid' => $item['xchan_guid'], + 'guid_sig' => $item['xchan_guid_sig'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $item['xchan_url']), + array('rel' => 'photo', 'type' => $item['xchan_photo_mimetype'], 'href' => $item['xchan_photo_m'])), + ), + )); + + + + $link = xmlify('' . "\n") ; + + $tagid = z_root() . '/search?tag=' . $term; + $objtype = ACTIVITY_OBJ_TAGTERM; + + $obj = json_encode(array( + 'type' => $objtype, + 'id' => $tagid, + 'link' => array(array('rel' => 'alternate','type' => 'text/html', 'href' => $tagid)), + 'title' => $term, + 'content' => $term + )); + + $bodyverb = t('%1$s tagged %2$s\'s %3$s with %4$s'); + + // saving here for reference + // also check out x22d5 and x2317 and x0d6b and x0db8 and x24d0 and xff20 !!! + + $termlink = html_entity_decode('⋕') . '[zrl=' . z_root() . '/search?tag=' . urlencode($term) . ']'. $term . '[/zrl]'; + + $channel = \App::get_channel(); + + $arr = array(); + + $arr['owner_xchan'] = $item['owner_xchan']; + $arr['author_xchan'] = $channel['channel_hash']; + + $arr['item_origin'] = 1; + $arr['item_wall'] = ((intval($item['item_wall'])) ? 1 : 0); + + $ulink = '[zrl=' . $channel['xchan_url'] . ']' . $channel['channel_name'] . '[/zrl]'; + $alink = '[zrl=' . $item['xchan_url'] . ']' . $item['xchan_name'] . '[/zrl]'; + $plink = '[zrl=' . $item['plink'] . ']' . $post_type . '[/zrl]'; + + $arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink, $termlink ); + + $arr['verb'] = ACTIVITY_TAG; + $arr['tgt_type'] = $targettype; + $arr['target'] = $target; + $arr['obj_type'] = $objtype; + $arr['obj'] = $obj; + $arr['parent_mid'] = $item['mid']; + + store_item_tag($item['uid'],$item['id'],TERM_OBJ_POST,TERM_COMMUNITYTAG,$term,$tagid); + $ret = post_activity_item($arr); + + if($ret['success']) { + build_sync_packet(local_channel(), + [ + 'item' => [ encode_item($ret['activity'],true) ] + ] + ); + } + + killme(); + + } + +} diff --git a/Zotlabs/Module/Tagrm.php b/Zotlabs/Module/Tagrm.php new file mode 100644 index 000000000..42aa6e90f --- /dev/null +++ b/Zotlabs/Module/Tagrm.php @@ -0,0 +1,147 @@ +' . t('Remove Item Tag') . ''; + + $o .= '

    ' . t('Select a tag to remove: ') . '

    '; + + $o .= '
    '; + $o .= ''; + $o .= '
      '; + + + foreach($r[0]['term'] as $x) { + $o .= '
    • ' . bbcode($x['term']) . '
    • '; + } + + $o .= '
    '; + $o .= ''; + $o .= ''; + $o .= '
    '; + + return $o; + + } + + } + +} diff --git a/Zotlabs/Module/Tasks.php b/Zotlabs/Module/Tasks.php new file mode 100644 index 000000000..6d0a92d91 --- /dev/null +++ b/Zotlabs/Module/Tasks.php @@ -0,0 +1,104 @@ + 1 && argv(1) === 'fetch') { + if(argc() > 2 && argv(2) === 'all') + $arr['all'] = 1; + + $x = tasks_fetch($arr); + if($x['tasks']) { + $x['html'] = ''; + foreach($x['tasks'] as $y) { + $x['html'] .= '
    ' . $y['summary'] . '
    '; + } + } + json_return_and_die($x); + } + + } + + + + function post() { + + + // logger('post: ' . print_r($_POST,true)); + + + if(! local_channel()) + return; + + $channel = \App::get_channel(); + + if((argc() > 2) && (argv(1) === 'complete') && intval(argv(2))) { + $ret = array('success' => false); + $r = q("select * from event where `etype` = 'task' and uid = %d and id = %d limit 1", + intval(local_channel()), + intval(argv(2)) + ); + if($r) { + $event = $r[0]; + if($event['event_status'] === 'COMPLETED') { + $event['event_status'] = 'IN-PROCESS'; + $event['event_status_date'] = NULL_DATE; + $event['event_percent'] = 0; + $event['event_sequence'] = $event['event_sequence'] + 1; + $event['edited'] = datetime_convert(); + } + else { + $event['event_status'] = 'COMPLETED'; + $event['event_status_date'] = datetime_convert(); + $event['event_percent'] = 100; + $event['event_sequence'] = $event['event_sequence'] + 1; + $event['edited'] = datetime_convert(); + } + $x = event_store_event($event); + if($x) + $ret['success'] = true; + } + json_return_and_die($ret); + } + + if(argc() == 2 && argv(1) === 'new') { + $text = escape_tags(trim($_REQUEST['summary'])); + if(! $text) + return array('success' => false); + $event = array(); + $event['account'] = $channel['channel_account_id']; + $event['uid'] = $channel['channel_id']; + $event['event_xchan'] = $channel['channel_hash']; + $event['etype'] = 'task'; + $event['nofinish'] = true; + $event['created'] = $event['edited'] = $event['dtstart'] = datetime_convert(); + $event['adjust'] = 1; + $event['allow_cid'] = '<' . $channel['channel_hash'] . '>'; + $event['summary'] = escape_tags($_REQUEST['summary']); + $x = event_store_event($event); + if($x) + $x['success'] = true; + else + $x = array('success' => false); + json_return_and_die($x); + } + } + + function get() { + if(! local_channel()) + return; + + return ''; + } +} diff --git a/Zotlabs/Module/Thing.php b/Zotlabs/Module/Thing.php new file mode 100644 index 000000000..65fc0588e --- /dev/null +++ b/Zotlabs/Module/Thing.php @@ -0,0 +1,368 @@ +set_from_array($_REQUEST); + } + + $x = $acl->get(); + + if($term_hash) { + $t = q("select * from obj where obj_obj = '%s' and obj_channel = %d limit 1", + dbesc($term_hash), + intval(local_channel()) + ); + if(! $t) { + notice( t('Item not found.') . EOL); + return; + } + $orig_record = $t[0]; + if($photo != $orig_record['obj_imgurl']) { + $arr = import_xchan_photo($photo,get_observer_hash(),true); + $local_photo = $arr[0]; + $local_photo_type = $arr[3]; + } + else + $local_photo = $orig_record['obj_imgurl']; + + $r = q("update obj set obj_term = '%s', obj_url = '%s', obj_imgurl = '%s', obj_edited = '%s', allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where obj_obj = '%s' and obj_channel = %d ", + dbesc($name), + dbesc(($url) ? $url : z_root() . '/thing/' . $term_hash), + dbesc($local_photo), + dbesc(datetime_convert()), + dbesc($x['allow_cid']), + dbesc($x['allow_gid']), + dbesc($x['deny_cid']), + dbesc($x['deny_gid']), + dbesc($term_hash), + intval(local_channel()) + ); + + info( t('Thing updated') . EOL); + + $r = q("select * from obj where obj_channel = %d and obj_obj = '%s' limit 1", + intval(local_channel()), + dbesc($term_hash) + ); + if($r) { + build_sync_packet(0, array('obj' => $r)); + } + + return; + } + + $sql = (($profile_guid) ? " and profile_guid = '" . dbesc($profile_guid) . "' " : " and is_default = 1 "); + $p = q("select profile_guid, is_default from profile where uid = %d $sql limit 1", + intval(local_channel()) + ); + + if($p) + $profile = $p[0]; + else + return; + + $local_photo = null; + + if($photo) { + $arr = import_xchan_photo($photo,get_observer_hash(),true); + $local_photo = $arr[0]; + $local_photo_type = $arr[3]; + } + + $created = datetime_convert(); + $url = (($url) ? $url : z_root() . '/thing/' . $hash); + + $r = q("insert into obj ( obj_page, obj_verb, obj_type, obj_channel, obj_obj, obj_term, obj_url, obj_imgurl, obj_created, obj_edited, allow_cid, allow_gid, deny_cid, deny_gid ) values ('%s','%s', %d, %d, '%s','%s','%s','%s','%s','%s','%s','%s','%s','%s') ", + dbesc($profile['profile_guid']), + dbesc($verb), + intval(TERM_OBJ_THING), + intval(local_channel()), + dbesc($hash), + dbesc($name), + dbesc($url), + dbesc(($photo) ? $local_photo : ''), + dbesc($created), + dbesc($created), + dbesc($x['allow_cid']), + dbesc($x['allow_gid']), + dbesc($x['deny_cid']), + dbesc($x['deny_gid']) + ); + + if(! $r) { + notice( t('Object store: failed')); + return; + } + + info( t('Thing added')); + + $r = q("select * from obj where obj_channel = %d and obj_obj = '%s' limit 1", + intval(local_channel()), + dbesc($hash) + ); + if($r) { + build_sync_packet(0, array('obj' => $r)); + } + + if($activity) { + $arr = array(); + $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $url)); + if($local_photo) + $links[] = array('rel' => 'photo', 'type' => $local_photo_type, 'href' => $local_photo); + + $objtype = ACTIVITY_OBJ_THING; + + $obj = json_encode(array( + 'type' => $objtype, + 'id' => $url, + 'link' => $links, + 'title' => $name, + 'content' => $name + )); + + $bodyverb = str_replace('OBJ: ', '',t('OBJ: %1$s %2$s %3$s')); + + $arr['owner_xchan'] = $channel['channel_hash']; + $arr['author_xchan'] = $channel['channel_hash']; + + $arr['item_origin'] = 1; + $arr['item_wall'] = 1; + $arr['item_thread_top'] = 1; + + $ulink = '[zrl=' . $channel['xchan_url'] . ']' . $channel['channel_name'] . '[/zrl]'; + $plink = '[zrl=' . $url . ']' . $name . '[/zrl]'; + + $arr['body'] = sprintf( $bodyverb, $ulink, $translated_verb, $plink ); + + if($local_photo) + $arr['body'] .= "\n\n[zmg]" . $local_photo . "[/zmg]"; + + $arr['verb'] = $verb; + $arr['obj_type'] = $objtype; + $arr['obj'] = $obj; + + if(! $profile['is_default']) { + $arr['item_private'] = true; + $str = ''; + $r = q("select abook_xchan from abook where abook_channel = %d and abook_profile = '%s'", + intval(local_channel()), + dbesc($profile_guid) + ); + if($r) { + $arr['allow_cid'] = ''; + foreach($r as $rr) + $arr['allow_cid'] .= '<' . $rr['abook_xchan'] . '>'; + } + else + $arr['allow_cid'] = '<' . get_observer_hash() . '>'; + } + + $ret = post_activity_item($arr); + } + } + + + function get() { + + // @FIXME one problem with things is we can't share them unless we provide the channel in the url + // so we can definitively lookup the owner. + + if(argc() == 2) { + + $r = q("select obj_channel from obj where obj_type = %d and obj_obj = '%s' limit 1", + intval(TERM_OBJ_THING), + dbesc(argv(1)) + ); + if($r) + $sql_extra = permissions_sql($r[0]['obj_channel']); + + $r = q("select * from obj where obj_type = %d and obj_obj = '%s' $sql_extra limit 1", + intval(TERM_OBJ_THING), + dbesc(argv(1)) + ); + + if($r) { + return replace_macros(get_markup_template('show_thing.tpl'), array( + '$header' => t('Show Thing'), + '$edit' => t('Edit'), + '$delete' => t('Delete'), + '$canedit' => ((local_channel() && local_channel() == $r[0]['obj_channel']) ? true : false), + '$thing' => $r[0] )); + } + else { + notice( t('item not found.') . EOL); + return; + } + } + + $channel = \App::get_channel(); + + if(! (local_channel() && $channel)) { + notice( t('Permission denied.') . EOL); + return; + } + + $acl = new \Zotlabs\Access\AccessList($channel); + $channel_acl = $acl->get(); + + $lockstate = (($acl->is_private()) ? 'lock' : 'unlock'); + + $thing_hash = ''; + + if(argc() == 3 && argv(1) === 'edit') { + $thing_hash = argv(2); + + $r = q("select * from obj where obj_type = %d and obj_obj = '%s' limit 1", + intval(TERM_OBJ_THING), + dbesc($thing_hash) + ); + + if((! $r) || ($r[0]['obj_channel'] != local_channel())) { + notice( t('Permission denied.') . EOL); + return ''; + } + + $o .= replace_macros(get_markup_template('thing_edit.tpl'),array( + '$thing_hdr' => t('Edit Thing'), + '$multiprof' => feature_enabled(local_channel(),'multi_profiles'), + '$profile_lbl' => t('Select a profile'), + '$profile_select' => contact_profile_assign($r[0]['obj_page']), + '$verb_lbl' => $channel['channel_name'], + '$verb_select' => obj_verb_selector($r[0]['obj_verb']), + '$activity' => array('activity',t('Post an activity'),true,t('Only sends to viewers of the applicable profile')), + '$thing_hash' => $thing_hash, + '$thing_lbl' => t('Name of thing e.g. something'), + '$thething' => $r[0]['obj_term'], + '$url_lbl' => t('URL of thing (optional)'), + '$theurl' => $r[0]['obj_url'], + '$img_lbl' => t('URL for photo of thing (optional)'), + '$imgurl' => $r[0]['obj_imgurl'], + '$permissions' => t('Permissions'), + '$aclselect' => populate_acl($channel_acl,false), + '$lockstate' => $lockstate, + '$submit' => t('Submit') + )); + + return $o; + } + + if(argc() == 3 && argv(1) === 'drop') { + $thing_hash = argv(2); + + $r = q("select * from obj where obj_type = %d and obj_obj = '%s' limit 1", + intval(TERM_OBJ_THING), + dbesc($thing_hash) + ); + + if((! $r) || ($r[0]['obj_channel'] != local_channel())) { + notice( t('Permission denied.') . EOL); + return ''; + } + + $x = q("delete from obj where obj_obj = '%s' and obj_type = %d and obj_channel = %d", + dbesc($thing_hash), + intval(TERM_OBJ_THING), + intval(local_channel()) + ); + + $r[0]['obj_deleted'] = 1; + + build_sync_packet(0,array('obj' => $r)); + + return $o; + } + + $o .= replace_macros(get_markup_template('thing_input.tpl'),array( + '$thing_hdr' => t('Add Thing to your Profile'), + '$multiprof' => feature_enabled(local_channel(),'multi_profiles'), + '$profile_lbl' => t('Select a profile'), + '$profile_select' => contact_profile_assign(''), + '$verb_lbl' => $channel['channel_name'], + '$activity' => array('activity',t('Post an activity'),((array_key_exists('activity',$_REQUEST)) ? $_REQUEST['activity'] : true),t('Only sends to viewers of the applicable profile')), + '$verb_select' => obj_verb_selector(), + '$thing_lbl' => t('Name of thing e.g. something'), + '$url_lbl' => t('URL of thing (optional)'), + '$img_lbl' => t('URL for photo of thing (optional)'), + '$permissions' => t('Permissions'), + '$aclselect' => populate_acl($channel_acl,false), + '$lockstate' => $lockstate, + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Toggle_mobile.php b/Zotlabs/Module/Toggle_mobile.php new file mode 100644 index 000000000..9d90c0821 --- /dev/null +++ b/Zotlabs/Module/Toggle_mobile.php @@ -0,0 +1,23 @@ + 1) { + $channel = \App::get_channel(); + + require_once('include/channel.php'); + + if(argc() > 1 && intval(argv(1)) > 1900) { + $year = intval(argv(1)); + } + + if(argc() > 2 && intval(argv(2)) > 0 && intval(argv(2)) <= 12) { + $month = intval(argv(2)); + } + + header('content-type: application/octet_stream'); + header('content-disposition: attachment; filename="' . $channel['channel_address'] . (($year) ? '-' . $year : '') . (($month) ? '-' . $month : '') . '.json"' ); + + if($year) { + echo json_encode(identity_export_year(local_channel(),$year,$month)); + killme(); + } + + if(argc() > 1 && argv(1) === 'basic') { + echo json_encode(identity_basic_export(local_channel())); + killme(); + } + + // FIXME - this basically doesn't work in the wild with a channel more than a few months old due to memory and execution time limits. + // It probably needs to be built at the CLI and offered to download as a tarball. Maybe stored in the members dav. + + if(argc() > 1 && argv(1) === 'complete') { + echo json_encode(identity_basic_export(local_channel(),true)); + killme(); + } + } + } + + function get() { + + $y = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); + + $yearurl = z_root() . '/uexport/' . $y; + $janurl = z_root() . '/uexport/' . $y . '/1'; + $impurl = '/import_items'; + $o = replace_macros(get_markup_template('uexport.tpl'), array( + '$title' => t('Export Channel'), + '$basictitle' => t('Export Channel'), + '$basic' => t('Export your basic channel information to a file. This acts as a backup of your connections, permissions, profile and basic data, which can be used to import your data to a new server hub, but does not contain your content.'), + '$fulltitle' => t('Export Content'), + '$full' => t('Export your channel information and recent content to a JSON backup that can be restored or imported to another server hub. This backs up all of your connections, permissions, profile data and several months of posts. This file may be VERY large. Please be patient - it may take several minutes for this download to begin.'), + '$by_year' => t('Export your posts from a given year.'), + + '$extra' => t('You may also export your posts and conversations for a particular year or month. Adjust the date in your browser location bar to select other dates. If the export fails (possibly due to memory exhaustion on your server hub), please try again selecting a more limited date range.'), + '$extra2' => sprintf( t('To select all posts for a given year, such as this year, visit %2$s'),$yearurl,$yearurl), + '$extra3' => sprintf( t('To select all posts for a given month, such as January of this year, visit %2$s'),$janurl,$janurl), + '$extra4' => sprintf( t('These content files may be imported or restored by visiting %2$s on any site containing your channel. For best results please import or restore these in date order (oldest first).'),$impurl,$impurl) + + )); + return $o; + } + +} diff --git a/Zotlabs/Module/Update_channel.php b/Zotlabs/Module/Update_channel.php new file mode 100644 index 000000000..b1b2d5103 --- /dev/null +++ b/Zotlabs/Module/Update_channel.php @@ -0,0 +1,70 @@ + 1) && (argv(1) == 'load')) ? 1 : 0); + + header("Content-type: text/html"); + echo "\r\n"; + + /** + * We can remove this hack once Internet Explorer recognises HTML5 natively + */ + + echo (($_GET['msie'] == 1) ? '
    ' : '
    '); + + /** + * + * Grab the page inner contents by calling the content function from the profile module directly, + * but move any image src attributes to another attribute name. This is because + * some browsers will prefetch all the images for the page even if we don't need them. + * The only ones we need to fetch are those for new page additions, which we'll discover + * on the client side and then swap the image back. + * + */ + + $mod = new Channel(); + + $text = $mod->get($profile_uid,$load); + + $pattern = "/]*) src=\"([^\"]*)\"/"; + $replace = "'; + $pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i"; + $text = preg_replace($pattern, $replace, $text); + } +*/ + + /** + * reportedly some versions of MSIE don't handle tabs in XMLHttpRequest documents very well + */ + + echo str_replace("\t",' ',$text); + echo (($_GET['msie'] == 1) ? '
    ' : ''); + echo "\r\n"; + killme(); + +} +} \ No newline at end of file diff --git a/Zotlabs/Module/Update_display.php b/Zotlabs/Module/Update_display.php new file mode 100644 index 000000000..13b04204d --- /dev/null +++ b/Zotlabs/Module/Update_display.php @@ -0,0 +1,48 @@ + 1) && (argv(1) == 'load')) ? 1 : 0); + header("Content-type: text/html"); + echo "\r\n"; + echo (($_GET['msie'] == 1) ? '
    ' : '
    '); + + $mod = new Display(); + $text = $mod->get($profile_uid, $load); + + $pattern = "/]*) src=\"([^\"]*)\"/"; + $replace = "'; + $pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i"; + $text = preg_replace($pattern, $replace, $text); + } + */ + echo str_replace("\t",' ',$text); + echo (($_GET['msie'] == 1) ? '
    ' : ''); + echo "\r\n"; + // logger('update_display: ' . $text); + killme(); + + } + +} diff --git a/Zotlabs/Module/Update_home.php b/Zotlabs/Module/Update_home.php new file mode 100644 index 000000000..0f699482e --- /dev/null +++ b/Zotlabs/Module/Update_home.php @@ -0,0 +1,42 @@ + 1) && (argv(1) == 'load')) ? 1 : 0); + header("Content-type: text/html"); + echo "\r\n"; + echo ((array_key_exists('msie',$_GET) && $_GET['msie'] == 1) ? '
    ' : '
    '); + + $mod = new Home(); + $text = $mod->get($profile_uid, $load); + + $pattern = "/]*) src=\"([^\"]*)\"/"; + $replace = "'; + $pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i"; + $text = preg_replace($pattern, $replace, $text); + } + */ + echo str_replace("\t",' ',$text); + echo ((array_key_exists('msie',$_GET) && $_GET['msie'] == 1) ? '
    ' : ''); + echo "\r\n"; + // logger('update_home: ' . $text); + killme(); + + } +} diff --git a/Zotlabs/Module/Update_network.php b/Zotlabs/Module/Update_network.php new file mode 100644 index 000000000..c27b7614a --- /dev/null +++ b/Zotlabs/Module/Update_network.php @@ -0,0 +1,44 @@ + 1) && (argv(1) == 'load')) ? 1 : 0); + header("Content-type: text/html"); + echo "\r\n"; + echo ((array_key_exists('msie',$_GET) && $_GET['msie'] == 1) ? '
    ' : '
    '); + + $mod = new Network(); + $text = $mod->get($profile_uid, $load); + + $pattern = "/]*) src=\"([^\"]*)\"/"; + $replace = "'; + $pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i"; + $text = preg_replace($pattern, $replace, $text); + } + */ + echo str_replace("\t",' ',$text); + echo ((array_key_exists('msie',$_GET) && $_GET['msie'] == 1) ? '
    ' : ''); + echo "\r\n"; + // logger('update_network: ' . $text); + killme(); + + } +} diff --git a/Zotlabs/Module/Update_pubstream.php b/Zotlabs/Module/Update_pubstream.php new file mode 100644 index 000000000..952b48df3 --- /dev/null +++ b/Zotlabs/Module/Update_pubstream.php @@ -0,0 +1,42 @@ + 1) && (argv(1) == 'load')) ? 1 : 0); + header("Content-type: text/html"); + echo "\r\n"; + echo ((array_key_exists('msie',$_GET) && $_GET['msie'] == 1) ? '
    ' : '
    '); + + $mod = new Pubstream(); + $text = $mod->get($profile_uid, $load); + + $pattern = "/]*) src=\"([^\"]*)\"/"; + $replace = "'; + $pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i"; + $text = preg_replace($pattern, $replace, $text); + } + */ + echo str_replace("\t",' ',$text); + echo ((array_key_exists('msie',$_GET) && $_GET['msie'] == 1) ? '
    ' : ''); + echo "\r\n"; + killme(); + + } +} diff --git a/Zotlabs/Module/Update_search.php b/Zotlabs/Module/Update_search.php new file mode 100644 index 000000000..4491f40f4 --- /dev/null +++ b/Zotlabs/Module/Update_search.php @@ -0,0 +1,69 @@ + 1) && (argv(1) == 'load')) ? 1 : 0); + + header("Content-type: text/html"); + echo "\r\n"; + + /** + * We can remove this hack once Internet Explorer recognises HTML5 natively + */ + + echo (($_GET['msie'] == 1) ? '
    ' : '
    '); + + /** + * + * Grab the page inner contents by calling the content function from the profile module directly, + * but move any image src attributes to another attribute name. This is because + * some browsers will prefetch all the images for the page even if we don't need them. + * The only ones we need to fetch are those for new page additions, which we'll discover + * on the client side and then swap the image back. + * + */ + + $mod = new Search(); + $text = $mod->get($profile_uid,$load); + + $pattern = "/]*) src=\"([^\"]*)\"/"; + $replace = "'; + $pattern = "/<\s*audio[^>]*>(.*?)<\s*\/\s*audio>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*video[^>]*>(.*?)<\s*\/\s*video>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*embed[^>]*>(.*?)<\s*\/\s*embed>/i"; + $text = preg_replace($pattern, $replace, $text); + $pattern = "/<\s*iframe[^>]*>(.*?)<\s*\/\s*iframe>/i"; + $text = preg_replace($pattern, $replace, $text); + } + */ + /** + * reportedly some versions of MSIE don't handle tabs in XMLHttpRequest documents very well + */ + + echo str_replace("\t",' ',$text); + echo (($_GET['msie'] == 1) ? '
    ' : ''); + echo "\r\n"; + killme(); + + } +} diff --git a/Zotlabs/Module/View.php b/Zotlabs/Module/View.php new file mode 100644 index 000000000..85497a2a4 --- /dev/null +++ b/Zotlabs/Module/View.php @@ -0,0 +1,20 @@ + 1) { + profile_load(argv(1)); + } + + } + + function get() { + + if(observer_prohibited()) { + notice( t('Public access denied.') . EOL); + return; + } + + if(((! count(\App::$profile)) || (\App::$profile['hide_friends']))) { + notice( t('Permission denied.') . EOL); + return; + } + + if(! perm_is_allowed(\App::$profile['uid'], get_observer_hash(),'view_contacts')) { + notice( t('Permission denied.') . EOL); + return; + } + + if(! $_REQUEST['aj']) + $_SESSION['return_url'] = \App::$query_string; + + + $is_owner = ((local_channel() && local_channel() == \App::$profile['uid']) ? true : false); + + $abook_flags = " and abook_pending = 0 and abook_self = 0 "; + $sql_extra = ''; + + if(! $is_owner) { + $abook_flags = " and abook_hidden = 0 "; + $sql_extra = " and xchan_hidden = 0 "; + } + + $r = q("SELECT count(*) as total FROM abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d $abook_flags and xchan_orphan = 0 and xchan_deleted = 0 $sql_extra ", + intval(\App::$profile['uid']) + ); + if($r) { + \App::set_pager_total($r[0]['total']); + } + + $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d $abook_flags and xchan_orphan = 0 and xchan_deleted = 0 $sql_extra order by xchan_name LIMIT %d OFFSET %d ", + intval(\App::$profile['uid']), + intval(\App::$pager['itemspage']), + intval(\App::$pager['start']) + ); + + if((! $r) && (! $_REQUEST['aj'])) { + info( t('No connections.') . EOL ); + return $o; + } + + $contacts = array(); + + foreach($r as $rr) { + + $url = chanlink_url($rr['xchan_url']); + if($url) { + $contacts[] = array( + 'id' => $rr['abook_id'], + 'archived' => (intval($rr['abook_archived']) ? true : false), + 'img_hover' => sprintf( t('Visit %s\'s profile [%s]'), $rr['xchan_name'], $rr['xchan_url']), + 'thumb' => $rr['xchan_photo_m'], + 'name' => substr($rr['xchan_name'],0,20), + 'username' => $rr['xchan_addr'], + 'link' => $url, + 'sparkle' => '', + 'itemurl' => $rr['url'], + 'network' => '', + ); + } + } + + + if($_REQUEST['aj']) { + if($contacts) { + $o = replace_macros(get_markup_template('viewcontactsajax.tpl'),array( + '$contacts' => $contacts + )); + } + else { + $o = '
    '; + } + echo $o; + killme(); + } + else { + $o .= ""; + $tpl = get_markup_template("viewcontact_template.tpl"); + $o .= replace_macros($tpl, array( + '$title' => t('View Connections'), + '$contacts' => $contacts, + // '$paginate' => paginate($a), + )); + } + + if(! $contacts) + $o .= '
    '; + + return $o; + } + +} diff --git a/Zotlabs/Module/Viewsrc.php b/Zotlabs/Module/Viewsrc.php new file mode 100644 index 000000000..fa755a3ec --- /dev/null +++ b/Zotlabs/Module/Viewsrc.php @@ -0,0 +1,53 @@ + 1) ? intval(argv(1)) : 0); + $json = ((argc() > 2 && argv(2) === 'json') ? true : false); + + if(! local_channel()) { + notice( t('Permission denied.') . EOL); + } + + + if(! $item_id) { + \App::$error = 404; + notice( t('Item not found.') . EOL); + } + + $item_normal = item_normal(); + + if(local_channel() && $item_id) { + $r = q("select id, item_flags, item_obscured, body from item where uid in (%d , %d) and id = %d $item_normal limit 1", + intval(local_channel()), + intval($sys['channel_id']), + intval($item_id) + ); + + if($r) { + if(intval($r[0]['item_obscured'])) + $r[0]['body'] = crypto_unencapsulate(json_decode($r[0]['body'],true),get_config('system','prvkey')); + $o = (($json) ? json_encode($r[0]['body']) : str_replace("\n",'
    ',$r[0]['body'])); + } + } + + if(is_ajax()) { + print '
    ' . t('Source of Item') . ' ' . $r[0]['id'] . '
    '; + echo $o; + killme(); + } + + return $o; + } + + +} diff --git a/Zotlabs/Module/Wall_attach.php b/Zotlabs/Module/Wall_attach.php new file mode 100644 index 000000000..9a1019ddb --- /dev/null +++ b/Zotlabs/Module/Wall_attach.php @@ -0,0 +1,55 @@ + 1) + $channel = get_channel_by_nick(argv(1)); + + if(! $channel) + killme(); + + $observer = \App::get_observer(); + + + $def_album = get_pconfig($channel['channel_id'],'system','photo_path'); + $def_attach = get_pconfig($channel['channel_id'],'system','attach_path'); + + $r = attach_store($channel,(($observer) ? $observer['xchan_hash'] : ''),'', array('source' => 'editor', 'visible' => 0, 'album' => $def_album, 'directory' => $def_attach, 'allow_cid' => '<' . $channel['channel_hash'] . '>')); + + if(! $r['success']) { + notice( $r['message'] . EOL); + killme(); + } + + if(intval($r['data']['is_photo'])) { + $s = "\n\n" . $r['body'] . "\n\n"; + } + else { + $s = "\n\n" . '[attachment]' . $r['data']['hash'] . ',' . $r['data']['revision'] . '[/attachment]' . "\n"; + } + + if($using_api) + return $s; + + echo $s; + killme(); + + } + +} diff --git a/Zotlabs/Module/Wall_upload.php b/Zotlabs/Module/Wall_upload.php new file mode 100644 index 000000000..3868cb14e --- /dev/null +++ b/Zotlabs/Module/Wall_upload.php @@ -0,0 +1,57 @@ + 1) + $nick = argv(1); + } + + $channel = (($nick) ? get_channel_by_nick($nick) : false); + + if(! $channel) { + if($using_api) + return; + notice( t('Channel not found.') . EOL); + killme(); + } + + $observer = \App::get_observer(); + + $args = array( 'source' => 'editor', 'visible' => 0, 'contact_allow' => array($channel['channel_hash'])); + + $ret = photo_upload($channel,$observer,$args); + + if(! $ret['success']) { + if($using_api) + return; + notice($ret['message']); + killme(); + } + + if($using_api) + return("\n\n" . $ret['body'] . "\n\n"); + else + echo "\n\n" . $ret['body'] . "\n\n"; + killme(); + } + +} diff --git a/Zotlabs/Module/Webfinger.php b/Zotlabs/Module/Webfinger.php new file mode 100644 index 000000000..c50680de7 --- /dev/null +++ b/Zotlabs/Module/Webfinger.php @@ -0,0 +1,54 @@ +Webfinger Diagnostic'; + + $o .= '
    '; + $o .= 'Lookup address: '; + $o .= '
    '; + + $o .= '

    '; + + $old = false; + if(x($_GET,'addr')) { + $addr = trim($_GET['addr']); + // if(strpos($addr,'@') !== false) { + $res = webfinger_rfc7033($addr,true); + if(! $res) { + $res = old_webfinger($addr); + $old = true; + } + // } + // else { + // if(function_exists('lrdd')) + // $res = lrdd($addr); + // } + + if($res && $old) { + foreach($res as $r) { + if($r['@attributes']['rel'] === 'http://microformats.org/profile/hcard') { + $hcard = unamp($r['@attributes']['href']); + require_once('library/HTML5/Parser.php'); + $res['vcard'] = scrape_vcard($hcard); + break; + } + } + } + + + $o .= '
    ';
    +			$o .= str_replace("\n",'
    ',print_r($res,true)); + $o .= '
    '; + } + return $o; + } + +} diff --git a/Zotlabs/Module/Webpages.php b/Zotlabs/Module/Webpages.php new file mode 100644 index 000000000..cc0a01cce --- /dev/null +++ b/Zotlabs/Module/Webpages.php @@ -0,0 +1,212 @@ + 1 && argv(1) === 'sys' && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + \App::$is_sys = true; + } + } + + if(argc() > 1) + $which = argv(1); + else + return; + + profile_load($which); + + } + + + function get() { + + if(! \App::$profile) { + notice( t('Requested profile is not available.') . EOL ); + \App::$error = 404; + return; + } + + $which = argv(1); + + $_SESSION['return_url'] = \App::$query_string; + + $uid = local_channel(); + $owner = 0; + $channel = null; + $observer = \App::get_observer(); + + $channel = \App::get_channel(); + + if(\App::$is_sys && is_site_admin()) { + $sys = get_sys_channel(); + if($sys && intval($sys['channel_id'])) { + $uid = $owner = intval($sys['channel_id']); + $channel = $sys; + $observer = $sys; + } + } + + if(! $owner) { + // Figure out who the page owner is. + $r = q("select channel_id from channel where channel_address = '%s'", + dbesc($which) + ); + if($r) { + $owner = intval($r[0]['channel_id']); + } + } + + $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); + + $perms = get_all_perms($owner,$ob_hash); + + if(! $perms['write_pages']) { + notice( t('Permission denied.') . EOL); + return; + } + + $mimetype = (($_REQUEST['mimetype']) ? $_REQUEST['mimetype'] : get_pconfig($owner,'system','page_mimetype')); + + $layout = (($_REQUEST['layout']) ? $_REQUEST['layout'] : get_pconfig($owner,'system','page_layout')); + + // Create a status editor (for now - we'll need a WYSIWYG eventually) to create pages + // Nickname is set to the observers xchan, and profile_uid to the owner's. + // This lets you post pages at other people's channels. + + if((! $channel) && ($uid) && ($uid == \App::$profile_uid)) { + $channel = \App::get_channel(); + } + if($channel) { + $channel_acl = array( + 'allow_cid' => $channel['channel_allow_cid'], + 'allow_gid' => $channel['channel_allow_gid'], + 'deny_cid' => $channel['channel_deny_cid'], + 'deny_gid' => $channel['channel_deny_gid'] + ); + } + else + $channel_acl = array(); + + $is_owner = ($uid && $uid == $owner); + $o = profile_tabs($a, $is_owner, \App::$profile['channel_address']); + + $x = array( + 'webpage' => ITEM_TYPE_WEBPAGE, + 'is_owner' => true, + 'nickname' => \App::$profile['channel_address'], + 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), + 'acl' => (($is_owner) ? populate_acl($channel_acl,false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')) : ''), + 'showacl' => (($is_owner) ? true : false), + 'visitor' => true, + 'hide_location' => true, + 'hide_voting' => true, + 'profile_uid' => intval($owner), + 'mimetype' => $mimetype, + 'mimeselect' => true, + 'layout' => $layout, + 'layoutselect' => true, + 'expanded' => true, + 'novoting'=> true, + 'bbco_autocomplete' => 'bbcode', + 'bbcode' => true + ); + + if($_REQUEST['title']) + $x['title'] = $_REQUEST['title']; + if($_REQUEST['body']) + $x['body'] = $_REQUEST['body']; + if($_REQUEST['pagetitle']) + $x['pagetitle'] = $_REQUEST['pagetitle']; + + $editor = status_editor($a,$x); + + // Get a list of webpages. We can't display all them because endless scroll makes that unusable, + // so just list titles and an edit link. + + + /** @TODO - this should be replaced with pagelist_widget */ + + $sql_extra = item_permissions_sql($owner); + + + $r = q("select * from iconfig left join item on iconfig.iid = item.id + where item.uid = %d and iconfig.cat = 'system' and iconfig.k = 'WEBPAGE' and item_type = %d + $sql_extra order by item.created desc", + intval($owner), + intval(ITEM_TYPE_WEBPAGE) + ); + +// $r = q("select * from item_id left join item on item_id.iid = item.id +// where item_id.uid = %d and service = 'WEBPAGE' and item_type = %d $sql_extra order by item.created desc", +// intval($owner), +// intval(ITEM_TYPE_WEBPAGE) +// ); + + $pages = null; + + if($r) { + $pages = array(); + foreach($r as $rr) { + unobscure($rr); + + $lockstate = (($rr['allow_cid'] || $rr['allow_gid'] || $rr['deny_cid'] || $rr['deny_gid']) ? 'lock' : 'unlock'); + + $element_arr = array( + 'type' => 'webpage', + 'title' => $rr['title'], + 'body' => $rr['body'], + 'created' => $rr['created'], + 'edited' => $rr['edited'], + 'mimetype' => $rr['mimetype'], + 'pagetitle' => $rr['v'], + 'mid' => $rr['mid'], + 'layout_mid' => $rr['layout_mid'] + ); + $pages[$rr['iid']][] = array( + 'url' => $rr['iid'], + 'pagetitle' => $rr['v'], + 'title' => $rr['title'], + 'created' => datetime_convert('UTC',date_default_timezone_get(),$rr['created']), + 'edited' => datetime_convert('UTC',date_default_timezone_get(),$rr['edited']), + 'bb_element' => '[element]' . base64url_encode(json_encode($element_arr)) . '[/element]', + 'lockstate' => $lockstate + ); + } + } + + + //Build the base URL for edit links + $url = z_root() . '/editwebpage/' . $which; + + $o .= replace_macros(get_markup_template('webpagelist.tpl'), array( + '$listtitle' => t('Webpages'), + '$baseurl' => $url, + '$create' => t('Create'), + '$edit' => t('Edit'), + '$share' => t('Share'), + '$delete' => t('Delete'), + '$pages' => $pages, + '$channel' => $which, + '$editor' => $editor, + '$view' => t('View'), + '$preview' => t('Preview'), + '$actions_txt' => t('Actions'), + '$pagelink_txt' => t('Page Link'), + '$title_txt' => t('Page Title'), + '$created_txt' => t('Created'), + '$edited_txt' => t('Edited') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Well_known.php b/Zotlabs/Module/Well_known.php new file mode 100644 index 000000000..b57666bff --- /dev/null +++ b/Zotlabs/Module/Well_known.php @@ -0,0 +1,69 @@ + 1) { + + $arr = array('server' => $_SERVER, 'request' => $_REQUEST); + call_hooks('well_known', $arr); + + + if(! check_siteallowed($_SERVER['REMOTE_ADDR'])) { + logger('well_known: site not allowed. ' . $_SERVER['REMOTE_ADDR']); + killme(); + } + + // from php.net re: REMOTE_HOST: + // Note: Your web server must be configured to create this variable. For example in Apache + // you'll need HostnameLookups On inside httpd.conf for it to exist. See also gethostbyaddr(). + + if(get_config('system','siteallowed_remote_host') && (! check_siteallowed($_SERVER['REMOTE_HOST']))) { + logger('well_known: site not allowed. ' . $_SERVER['REMOTE_HOST']); + killme(); + } + + + switch(argv(1)) { + case 'zot-info': + \App::$argc -= 1; + array_shift(\App::$argv); + \App::$argv[0] = 'zfinger'; + $module = new \Zotlabs\Module\Zfinger(); + $module->init(); + break; + + case 'webfinger': + \App::$argc -= 1; + array_shift(\App::$argv); + \App::$argv[0] = 'wfinger'; + $module = new \Zotlabs\Module\Wfinger(); + $module->init(); + break; + + case 'host-meta': + \App::$argc -= 1; + array_shift(\App::$argv); + \App::$argv[0] = 'hostxrd'; + $module = new \Zotlabs\Module\Hostxrd(); + $module->init(); + break; + + default: + if(file_exists(\App::$cmd)) { + echo file_get_contents(\App::$cmd); + killme(); + } + elseif(file_exists(\App::$cmd . '.php')) + require_once(\App::$cmd . '.php'); + break; + + } + } + + http_status_exit(404); + } +} diff --git a/Zotlabs/Module/Wfinger.php b/Zotlabs/Module/Wfinger.php new file mode 100644 index 000000000..fa1e11518 --- /dev/null +++ b/Zotlabs/Module/Wfinger.php @@ -0,0 +1,140 @@ + $r[0]['channel_name'], + 'http://xmlns.com/foaf/0.1/name' => $r[0]['channel_name'] + ); + + foreach($aliases as $alias) + if($alias != $resource) + $result['aliases'][] = $alias; + + $result['links'] = array( + + array( + 'rel' => 'http://webfinger.net/rel/avatar', + 'type' => $r[0]['xchan_photo_mimetype'], + 'href' => $r[0]['xchan_photo_l'] + ), + + array( + 'rel' => 'http://webfinger.net/rel/profile-page', + 'href' => z_root() . '/profile/' . $r[0]['channel_address'], + ), + + array( + 'rel' => 'http://webfinger.net/rel/blog', + 'href' => z_root() . '/channel/' . $r[0]['channel_address'], + ), + + array( + 'rel' => 'http://ostatus.org/schema/1.0/subscribe', + 'template' => z_root() . '/follow/url={uri}', + ), + + array( + 'rel' => 'http://purl.org/zot/protocol', + 'href' => z_root() . '/.well-known/zot-info' . '?address=' . $r[0]['xchan_addr'], + ), + + array( + 'rel' => 'magic-public-key', + 'href' => 'data:application/magic-public-key,' . salmon_key($r[0]['channel_pubkey']), + ) + ); + + if($zot) { + // get a zotinfo packet and return it with webfinger + $result['zot'] = zotinfo(array('address' => $r[0]['xchan_addr'])); + } + } + else { + header($_SERVER["SERVER_PROTOCOL"] . ' ' . 400 . ' ' . 'Bad Request'); + killme(); + } + + $arr = array('channel' => $r[0], 'request' => $_REQUEST, 'result' => $result); + call_hooks('webfinger',$arr); + + json_return_and_die($arr['result'],'application/jrd+json'); + + } + +} diff --git a/Zotlabs/Module/Wiki.php b/Zotlabs/Module/Wiki.php new file mode 100644 index 000000000..55a52ea6d --- /dev/null +++ b/Zotlabs/Module/Wiki.php @@ -0,0 +1,536 @@ + 1) + $nick = argv(1); // if the channel name is in the URL, use that + if (!$nick && local_channel()) { // if no channel name was provided, assume the current logged in channel + $channel = \App::get_channel(); + if ($channel && $channel['channel_address']) { + $nick = $channel['channel_address']; + goaway(z_root() . '/wiki/' . $nick); + } + } + if (!$nick) { + notice(t('You must be logged in to see this page.') . EOL); + goaway('/login'); + } + profile_load($nick); + + } + + function get() { + + if(observer_prohibited(true)) { + return login(); + } + + if(! feature_enabled(\App::$profile_uid,'wiki')) { + notice( t('Not found') . EOL); + return; + } + + $tab = 'wiki'; + + + require_once('include/wiki.php'); + require_once('include/acl_selectors.php'); + require_once('include/conversation.php'); + + // TODO: Combine the interface configuration into a unified object + // Something like $interface = array('new_page_button' => false, 'new_wiki_button' => false, ...) + $wiki_owner = false; + $showNewWikiButton = false; + $showCommitMsg = false; + $hidePageHistory = false; + $pageHistory = array(); + $local_observer = null; + $resource_id = ''; + + // init() should have forced the URL to redirect to /wiki/channel so assume argc() > 1 + $nick = argv(1); + $channel = get_channel_by_nick($nick); // The channel who owns the wikis being viewed + if(! $channel) { + notice('Invalid channel' . EOL); + goaway('/' . argv(0)); + } + // Determine if the observer is the channel owner so the ACL dialog can be populated + if (local_channel() === intval($channel['channel_id'])) { + $local_observer = \App::get_channel(); + $wiki_owner = true; + + // Obtain the default permission settings of the channel + $channel_acl = array( + 'allow_cid' => $local_observer['channel_allow_cid'], + 'allow_gid' => $local_observer['channel_allow_gid'], + 'deny_cid' => $local_observer['channel_deny_cid'], + 'deny_gid' => $local_observer['channel_deny_gid'] + ); + // Initialize the ACL to the channel default permissions + $x = array( + 'lockstate' => (( $local_observer['channel_allow_cid'] || + $local_observer['channel_allow_gid'] || + $local_observer['channel_deny_cid'] || + $local_observer['channel_deny_gid']) + ? 'lock' : 'unlock'), + 'acl' => populate_acl($channel_acl), + 'bang' => '' + ); + } else { + // Not the channel owner + $channel_acl = $x = array(); + } + + switch (argc()) { + case 2: + // Configure page template + $wikiheaderName = t('Wiki'); + $wikiheaderPage = t('Sandbox'); + require_once('library/markdown.php'); + $content = t('"# Wiki Sandbox\n\nContent you **edit** and **preview** here *will not be saved*."'); + $renderedContent = Markdown(json_decode($content)); + $hide_editor = false; + $showPageControls = false; + $showNewWikiButton = $wiki_owner; + $showNewPageButton = false; + $hidePageHistory = true; + $showCommitMsg = false; + break; + case 3: + // /wiki/channel/wiki -> No page was specified, so redirect to Home.md + $wikiUrlName = urlencode(argv(2)); + goaway('/'.argv(0).'/'.argv(1).'/'.$wikiUrlName.'/Home'); + case 4: + // GET /wiki/channel/wiki/page + // Fetch the wiki info and determine observer permissions + $wikiUrlName = urlencode(argv(2)); + $pageUrlName = urlencode(argv(3)); + $w = wiki_exists_by_name($channel['channel_id'], $wikiUrlName); + if(!$w['resource_id']) { + notice('Wiki not found' . EOL); + goaway('/'.argv(0).'/'.argv(1)); + } + $resource_id = $w['resource_id']; + + if (!$wiki_owner) { + // Check for observer permissions + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['read']) { + notice('Permission denied.' . EOL); + goaway('/'.argv(0).'/'.argv(1)); + } + if($perms['write']) { + $wiki_editor = true; + } else { + $wiki_editor = false; + } + } else { + $wiki_editor = true; + } + $wikiheaderName = urldecode($wikiUrlName); + $wikiheaderPage = urldecode($pageUrlName); + $p = wiki_get_page_content(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + if(!$p['success']) { + notice('Error retrieving page content' . EOL); + goaway('/'.argv(0).'/'.argv(1).'/'.$wikiUrlName); + } + $content = ($p['content'] !== '' ? htmlspecialchars_decode($p['content'],ENT_COMPAT) : '"# New page\n"'); + // Render the Markdown-formatted page content in HTML + require_once('library/markdown.php'); + $html = wiki_generate_toc(purify_html(Markdown(json_decode($content)))); + $renderedContent = wiki_convert_links($html,argv(0).'/'.argv(1).'/'.$wikiUrlName); + $hide_editor = false; + $showPageControls = $wiki_editor; + $showNewWikiButton = $wiki_owner; + $showNewPageButton = $wiki_editor; + $hidePageHistory = false; + $showCommitMsg = true; + $pageHistory = wiki_page_history(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + break; + default: // Strip the extraneous URL components + goaway('/'.argv(0).'/'.argv(1).'/'.$wikiUrlName.'/'.$pageUrlName); + } + + $wikiModalID = random_string(3); + $wikiModal = replace_macros( + get_markup_template('generic_modal.tpl'), array( + '$id' => $wikiModalID, + '$title' => t('Revision Comparison'), + '$ok' => t('Revert'), + '$cancel' => t('Cancel') + ) + ); + + $is_owner = ((local_channel()) && (local_channel() == \App::$profile['profile_uid']) ? true : false); + + $o .= profile_tabs($a, $is_owner, \App::$profile['channel_address']); + + + $o .= replace_macros(get_markup_template('wiki.tpl'),array( + '$wikiheaderName' => $wikiheaderName, + '$wikiheaderPage' => $wikiheaderPage, + '$hideEditor' => $hide_editor, + '$showPageControls' => $showPageControls, + '$showNewWikiButton'=> $showNewWikiButton, + '$showNewPageButton'=> $showNewPageButton, + '$hidePageHistory' => $hidePageHistory, + '$showCommitMsg' => $showCommitMsg, + '$channel' => $channel['channel_address'], + '$resource_id' => $resource_id, + '$page' => $pageUrlName, + '$lockstate' => $x['lockstate'], + '$acl' => $x['acl'], + '$bang' => $x['bang'], + '$content' => $content, + '$renderedContent' => $renderedContent, + '$wikiName' => array('wikiName', t('Enter the name of your new wiki:'), '', ''), + '$pageName' => array('pageName', t('Enter the name of the new page:'), '', ''), + '$pageRename' => array('pageRename', t('Enter the new name:'), '', ''), + '$commitMsg' => array('commitMsg', '', '', '', '', 'placeholder="(optional) Enter a custom message when saving the page..."'), + '$pageHistory' => $pageHistory['history'], + '$wikiModal' => $wikiModal, + '$wikiModalID' => $wikiModalID, + '$commit' => 'HEAD', + '$embedPhotos' => t('Embed image from photo albums'), + '$embedPhotosModalTitle' => t('Embed an image from your albums'), + '$embedPhotosModalCancel' => t('Cancel'), + '$embedPhotosModalOK' => t('OK'), + '$modalchooseimages' => t('Choose images to embed'), + '$modalchoosealbum' => t('Choose an album'), + '$modaldiffalbum' => t('Choose a different album...'), + '$modalerrorlist' => t('Error getting album list'), + '$modalerrorlink' => t('Error getting photo link'), + '$modalerroralbum' => t('Error getting album'), + )); + head_add_js('library/ace/ace.js'); // Ace Code Editor + return $o; + } + + function post() { + require_once('include/wiki.php'); + + // /wiki/channel/preview + // Render mardown-formatted text in HTML for preview + if((argc() > 2) && (argv(2) === 'preview')) { + $content = $_POST['content']; + $resource_id = $_POST['resource_id']; + require_once('library/markdown.php'); + $html = wiki_generate_toc(purify_html(Markdown($content))); + $w = wiki_get_wiki($resource_id); + $wikiURL = argv(0).'/'.argv(1).'/'.$w['urlName']; + $html = wiki_convert_links($html,$wikiURL); + json_return_and_die(array('html' => $html, 'success' => true)); + } + + // Create a new wiki + // /wiki/channel/create/wiki + if ((argc() > 3) && (argv(2) === 'create') && (argv(3) === 'wiki')) { + $nick = argv(1); + $channel = get_channel_by_nick($nick); + // Determine if observer has permission to create wiki + $observer_hash = get_observer_hash(); + // Only the channel owner can create a wiki, at least until we create a + // more detail permissions framework + if (local_channel() !== intval($channel['channel_id'])) { + goaway('/'.argv(0).'/'.$nick.'/'); + } + $wiki = array(); + // Generate new wiki info from input name + $wiki['postVisible'] = ((intval($_POST['postVisible']) === 0) ? 0 : 1); + $wiki['rawName'] = $_POST['wikiName']; + $wiki['htmlName'] = escape_tags($_POST['wikiName']); + $wiki['urlName'] = urlencode($_POST['wikiName']); + if($wiki['urlName'] === '') { + notice('Error creating wiki. Invalid name.'); + goaway('/wiki'); + } + // Get ACL for permissions + $acl = new \Zotlabs\Access\AccessList($channel); + $acl->set_from_array($_POST); + $r = wiki_create_wiki($channel, $observer_hash, $wiki, $acl); + if ($r['success']) { + $homePage = wiki_create_page('Home', $r['item']['resource_id']); + if(!$homePage['success']) { + notice('Wiki created, but error creating Home page.'); + goaway('/wiki/'.$nick.'/'.$wiki['urlName']); + } + goaway('/wiki/'.$nick.'/'.$wiki['urlName'].'/'.$homePage['page']['urlName']); + } else { + notice('Error creating wiki'); + goaway('/wiki'); + } + } + + // Delete a wiki + if ((argc() > 3) && (argv(2) === 'delete') && (argv(3) === 'wiki')) { + $nick = argv(1); + $channel = get_channel_by_nick($nick); + // Only the channel owner can delete a wiki, at least until we create a + // more detail permissions framework + if (local_channel() !== intval($channel['channel_id'])) { + logger('Wiki delete permission denied.' . EOL); + json_return_and_die(array('message' => 'Wiki delete permission denied.', 'success' => false)); + } + $resource_id = $_POST['resource_id']; + $deleted = wiki_delete_wiki($resource_id); + if ($deleted['success']) { + json_return_and_die(array('message' => '', 'success' => true)); + } else { + logger('Error deleting wiki: ' . $resource_id); + json_return_and_die(array('message' => 'Error deleting wiki', 'success' => false)); + } + } + + // Create a page + if ((argc() === 4) && (argv(2) === 'create') && (argv(3) === 'page')) { + $nick = argv(1); + $resource_id = $_POST['resource_id']; + // Determine if observer has permission to create a page + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['write']) { + logger('Wiki write permission denied. ' . EOL); + json_return_and_die(array('success' => false)); + } + } + $name = $_POST['name']; //Get new page name + if(urlencode(escape_tags($_POST['name'])) === '') { + json_return_and_die(array('message' => 'Error creating page. Invalid name.', 'success' => false)); + } + $page = wiki_create_page($name, $resource_id); + if ($page['success']) { + json_return_and_die(array('url' => '/'.argv(0).'/'.argv(1).'/'.$page['wiki']['urlName'].'/'.urlencode($page['page']['urlName']), 'success' => true)); + } else { + logger('Error creating page'); + json_return_and_die(array('message' => 'Error creating page.', 'success' => false)); + } + } + + // Fetch page list for a wiki + if ((argc() === 5) && (argv(2) === 'get') && (argv(3) === 'page') && (argv(4) === 'list')) { + $resource_id = $_POST['resource_id']; // resource_id for wiki in db + $channel = get_channel_by_nick(argv(1)); + $observer_hash = get_observer_hash(); + if (local_channel() !== intval($channel['channel_id'])) { + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['read']) { + logger('Wiki read permission denied.' . EOL); + json_return_and_die(array('pages' => null, 'message' => 'Permission denied.', 'success' => false)); + } + } + $page_list_html = widget_wiki_pages(array( + 'resource_id' => $resource_id, + 'refresh' => true, + 'channel' => argv(1))); + json_return_and_die(array('pages' => $page_list_html, 'message' => '', 'success' => true)); + } + + // Save a page + if ((argc() === 4) && (argv(2) === 'save') && (argv(3) === 'page')) { + + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['name']; + $pageHtmlName = escape_tags($_POST['name']); + $content = $_POST['content']; //Get new content + $commitMsg = $_POST['commitMsg']; + if ($commitMsg === '') { + $commitMsg = 'Updated ' . $pageHtmlName; + } + $nick = argv(1); + $channel = get_channel_by_nick($nick); + // Determine if observer has permission to save content + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['write']) { + logger('Wiki write permission denied. ' . EOL); + json_return_and_die(array('success' => false)); + } + } + + $saved = wiki_save_page(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'content' => $content)); + if($saved['success']) { + $ob = \App::get_observer(); + $commit = wiki_git_commit(array( + 'commit_msg' => $commitMsg, + 'resource_id' => $resource_id, + 'observer' => $ob, + 'files' => array($pageUrlName.'.md') + )); + if($commit['success']) { + json_return_and_die(array('message' => 'Wiki git repo commit made', 'success' => true)); + } else { + json_return_and_die(array('message' => 'Error making git commit','success' => false)); + } + } else { + json_return_and_die(array('message' => 'Error saving page', 'success' => false)); + } + } + + // Update page history + // /wiki/channel/history/page + if ((argc() === 4) && (argv(2) === 'history') && (argv(3) === 'page')) { + + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['name']; + + $nick = argv(1); + $channel = get_channel_by_nick($nick); + // Determine if observer has permission to read content + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['read']) { + logger('Wiki read permission denied.' . EOL); + json_return_and_die(array('historyHTML' => '', 'message' => 'Permission denied.', 'success' => false)); + } + } + $historyHTML = widget_wiki_page_history(array( + 'resource_id' => $resource_id, + 'pageUrlName' => $pageUrlName + )); + json_return_and_die(array('historyHTML' => $historyHTML, 'message' => '', 'success' => true)); + } + + // Delete a page + if ((argc() === 4) && (argv(2) === 'delete') && (argv(3) === 'page')) { + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['name']; + if ($pageUrlName === 'Home') { + json_return_and_die(array('message' => 'Cannot delete Home','success' => false)); + } + // Determine if observer has permission to delete pages + $nick = argv(1); + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['write']) { + logger('Wiki write permission denied. ' . EOL); + json_return_and_die(array('success' => false)); + } + } + $deleted = wiki_delete_page(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + if($deleted['success']) { + $ob = \App::get_observer(); + $commit = wiki_git_commit(array( + 'commit_msg' => 'Deleted ' . $pageUrlName, + 'resource_id' => $resource_id, + 'observer' => $ob, + 'files' => null + )); + if($commit['success']) { + json_return_and_die(array('message' => 'Wiki git repo commit made', 'success' => true)); + } else { + json_return_and_die(array('message' => 'Error making git commit','success' => false)); + } + } else { + json_return_and_die(array('message' => 'Error deleting page', 'success' => false)); + } + } + + // Revert a page + if ((argc() === 4) && (argv(2) === 'revert') && (argv(3) === 'page')) { + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['name']; + $commitHash = $_POST['commitHash']; + // Determine if observer has permission to revert pages + $nick = argv(1); + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['write']) { + logger('Wiki write permission denied.' . EOL); + json_return_and_die(array('success' => false)); + } + } + $reverted = wiki_revert_page(array('commitHash' => $commitHash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + if($reverted['success']) { + json_return_and_die(array('content' => $reverted['content'], 'message' => '', 'success' => true)); + } else { + json_return_and_die(array('content' => '', 'message' => 'Error reverting page', 'success' => false)); + } + } + + // Compare page revisions + if ((argc() === 4) && (argv(2) === 'compare') && (argv(3) === 'page')) { + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['name']; + $compareCommit = $_POST['compareCommit']; + $currentCommit = $_POST['currentCommit']; + // Determine if observer has permission to revert pages + $nick = argv(1); + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['read']) { + logger('Wiki read permission denied.' . EOL); + json_return_and_die(array('success' => false)); + } + } + $compare = wiki_compare_page(array('currentCommit' => $currentCommit, 'compareCommit' => $compareCommit, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + if($compare['success']) { + $diffHTML = '
    Current RevisionSelected Revision
    ' . $compare['diff']; + json_return_and_die(array('diff' => $diffHTML, 'message' => '', 'success' => true)); + } else { + json_return_and_die(array('diff' => '', 'message' => 'Error comparing page', 'success' => false)); + } + } + + // Rename a page + if ((argc() === 4) && (argv(2) === 'rename') && (argv(3) === 'page')) { + $resource_id = $_POST['resource_id']; + $pageUrlName = $_POST['oldName']; + $pageNewName = $_POST['newName']; + if ($pageUrlName === 'Home') { + json_return_and_die(array('message' => 'Cannot rename Home','success' => false)); + } + if(urlencode(escape_tags($pageNewName)) === '') { + json_return_and_die(array('message' => 'Error renaming page. Invalid name.', 'success' => false)); + } + // Determine if observer has permission to rename pages + $nick = argv(1); + $channel = get_channel_by_nick($nick); + if (local_channel() !== intval($channel['channel_id'])) { + $observer_hash = get_observer_hash(); + $perms = wiki_get_permissions($resource_id, intval($channel['channel_id']), $observer_hash); + if(!$perms['write']) { + logger('Wiki write permission denied. ' . EOL); + json_return_and_die(array('success' => false)); + } + } + $renamed = wiki_rename_page(array('resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'pageNewName' => $pageNewName)); + if($renamed['success']) { + $ob = \App::get_observer(); + $commit = wiki_git_commit(array( + 'commit_msg' => 'Renamed ' . urldecode($pageUrlName) . ' to ' . $renamed['page']['htmlName'], + 'resource_id' => $resource_id, + 'observer' => $ob, + 'files' => array($pageUrlName . '.md', $renamed['page']['fileName']), + 'all' => true + )); + if($commit['success']) { + json_return_and_die(array('name' => $renamed['page'], 'message' => 'Wiki git repo commit made', 'success' => true)); + } else { + json_return_and_die(array('message' => 'Error making git commit','success' => false)); + } + } else { + json_return_and_die(array('message' => 'Error renaming page', 'success' => false)); + } + } + + //notice('You must be authenticated.'); + json_return_and_die(array('message' => 'You must be authenticated.', 'success' => false)); + + } +} diff --git a/Zotlabs/Module/Xchan.php b/Zotlabs/Module/Xchan.php new file mode 100644 index 000000000..526580fad --- /dev/null +++ b/Zotlabs/Module/Xchan.php @@ -0,0 +1,47 @@ +' . t('Xchan Lookup') . ''; + + $o .= '
    '; + $o .= t('Lookup xchan beginning with (or webbie): '); + $o .= ''; + $o .= '
    '; + $o .= '

    '; + + if(x($_GET, 'addr')) { + $addr = trim($_GET['addr']); + + $r = q("select * from xchan where xchan_hash like '%s%%' or xchan_addr = '%s' group by xchan_hash", + dbesc($addr), + dbesc($addr) + ); + + if($r) { + foreach($r as $rr) { + $o .= str_replace(array("\n", " "), array("
    ", " "), print_r($rr, true)) . EOL; + + $s = q("select * from hubloc where hubloc_hash like '%s'", + dbesc($r[0]['xchan_hash']) + ); + + if($s) { + foreach($s as $rrr) + $o .= str_replace(array("\n", " "), array("
    ", " "), print_r($rrr, true)) . EOL; + } + } + } + else + notice( t('Not found.') . EOL); + + } + return $o; + } + +} diff --git a/Zotlabs/Module/Xpoco.php b/Zotlabs/Module/Xpoco.php new file mode 100644 index 000000000..3ff05c4e1 --- /dev/null +++ b/Zotlabs/Module/Xpoco.php @@ -0,0 +1,13 @@ + z_root(), + '$dspr_guid' => $r[0]['channel_guid'] . str_replace('.','',\App::get_hostname()), + '$dspr_key' => base64_encode(pemtorsa($r[0]['channel_pubkey'])) + )); + + $salmon_key = salmon_key($r[0]['channel_pubkey']); + + header('Access-Control-Allow-Origin: *'); + header("Content-type: application/xrd+xml"); + + + $aliases = array('acct:' . $r[0]['channel_address'] . '@' . \App::get_hostname(), z_root() . '/channel/' . $r[0]['channel_address'], z_root() . '/~' . $r[0]['channel_address']); + + for($x = 0; $x < count($aliases); $x ++) { + if($aliases[$x] === $resource) + unset($aliases[$x]); + } + + + $o = replace_macros(get_markup_template('xrd_person.tpl'), array( + '$nick' => $r[0]['channel_address'], + '$accturi' => $resource, + '$aliases' => $aliases, + '$profile_url' => z_root() . '/channel/' . $r[0]['channel_address'], + '$hcard_url' => z_root() . '/hcard/' . $r[0]['channel_address'], + '$atom' => z_root() . '/feed/' . $r[0]['channel_address'], + '$zot_post' => z_root() . '/post/' . $r[0]['channel_address'], + '$poco_url' => z_root() . '/poco/' . $r[0]['channel_address'], + '$photo' => z_root() . '/photo/profile/l/' . $r[0]['channel_id'], + '$dspr' => $dspr, + // '$salmon' => z_root() . '/salmon/' . $r[0]['channel_address'], + // '$salmen' => z_root() . '/salmon/' . $r[0]['channel_address'] . '/mention', + '$modexp' => 'data:application/magic-public-key,' . $salmon_key, + '$subscribe' => z_root() . '/follow?url={uri}', + '$bigkey' => salmon_key($r[0]['channel_pubkey']) + )); + + + $arr = array('user' => $r[0], 'xml' => $o); + call_hooks('personal_xrd', $arr); + + echo $arr['xml']; + killme(); + + } + +} diff --git a/Zotlabs/Module/Xref.php b/Zotlabs/Module/Xref.php new file mode 100644 index 000000000..e9d494da4 --- /dev/null +++ b/Zotlabs/Module/Xref.php @@ -0,0 +1,26 @@ + 2) + $url = argv(2); + + goaway (z_root() . '/' . $url); + + } + +} diff --git a/Zotlabs/Module/Zfinger.php b/Zotlabs/Module/Zfinger.php new file mode 100644 index 000000000..2ff605fc9 --- /dev/null +++ b/Zotlabs/Module/Zfinger.php @@ -0,0 +1,18 @@ + false); + + $mindate = (($_REQUEST['mindate']) ? datetime_convert('UTC','UTC',$_REQUEST['mindate']) : ''); + if(! $mindate) + $mindate = datetime_convert('UTC','UTC', 'now - 14 days'); + + if(observer_prohibited()) { + $result['message'] = 'Public access denied'; + json_return_and_die($result); + } + + $observer = \App::get_observer(); + + + $channel_address = ((argc() > 1) ? argv(1) : ''); + if($channel_address) { + $r = q("select channel_id, channel_name from channel where channel_address = '%s' and channel_removed = 0 limit 1", + dbesc(argv(1)) + ); + } + else { + $x = get_sys_channel(); + if($x) + $r = array($x); + $mindate = datetime_convert('UTC','UTC', 'now - 14 days'); + } + if(! $r) { + $result['message'] = 'Channel not found.'; + json_return_and_die($result); + } + + logger('zotfeed request: ' . $r[0]['channel_name'], LOGGER_DEBUG); + + $result['messages'] = zot_feed($r[0]['channel_id'],$observer['xchan_hash'],array('mindate' => $mindate)); + $result['success'] = true; + json_return_and_die($result); + } + +} diff --git a/Zotlabs/Module/Zping.php b/Zotlabs/Module/Zping.php new file mode 100644 index 000000000..d6128fa66 --- /dev/null +++ b/Zotlabs/Module/Zping.php @@ -0,0 +1,33 @@ +test_condition($mtch[1])) { + $s = str_replace($mtch[0], $mtch[2], $s); + } + else { + $s = str_replace($mtch[0], $mtch[3], $s); + } + } + } + else { + $cnt = preg_match_all("/\[if (.*?)\](.*?)\[\/if\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + if($this->test_condition($mtch[1])) { + $s = str_replace($mtch[0], $mtch[2], $s); + } + else { + $s = str_replace($mtch[0], '', $s); + } + } + } + } + if($pass == 0) + $this->parse_pass0($s); + else + $this->parse_pass1($s); + + } + + function parse_pass0($s) { + + $matches = null; + + $cnt = preg_match("/\[layout\](.*?)\[\/layout\]/ism", $s, $matches); + if($cnt) + \App::$page['template'] = trim($matches[1]); + + $cnt = preg_match("/\[template=(.*?)\](.*?)\[\/template\]/ism", $s, $matches); + if($cnt) { + \App::$page['template'] = trim($matches[2]); + \App::$page['template_style'] = trim($matches[2]) . '_' . $matches[1]; + } + + $cnt = preg_match("/\[template\](.*?)\[\/template\]/ism", $s, $matches); + if($cnt) { + \App::$page['template'] = trim($matches[1]); + } + + $cnt = preg_match("/\[theme=(.*?)\](.*?)\[\/theme\]/ism", $s, $matches); + if($cnt) { + \App::$layout['schema'] = trim($matches[1]); + \App::$layout['theme'] = trim($matches[2]); + } + + $cnt = preg_match("/\[theme\](.*?)\[\/theme\]/ism", $s, $matches); + if($cnt) + \App::$layout['theme'] = trim($matches[1]); + + $cnt = preg_match_all("/\[webpage\](.*?)\[\/webpage\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + // only the last webpage definition is used if there is more than one + foreach($matches as $mtch) { + \App::$layout['webpage'] = $this->webpage($a,$mtch[1]); + } + } + } + + function parse_pass1($s) { + $cnt = preg_match_all("/\[region=(.*?)\](.*?)\[\/region\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + \App::$layout['region_' . $mtch[1]] = $this->region($mtch[2],$mtch[1]); + } + } + } + + + + function test_condition($s) { + + // This is extensible. The first version of variable testing supports tests of the form + // [if $config.system.foo] which will check for a return of a true condition for get_config('system','foo'); + // The values 0, '', an empty array, and an unset value will all evaluate to false. + + if(preg_match("/[\$]config[\.](.*?)/",$s,$matches)) { + $x = explode('.',$s); + if(get_config($x[1],$x[2])) + return true; + } + return false; + + } + + + function menu($s, $class = '') { + + $channel_id = $this->get_channel_id(); + $name = $s; + + $cnt = preg_match_all("/\[var=(.*?)\](.*?)\[\/var\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $var[$mtch[1]] = $mtch[2]; + $name = str_replace($mtch[0], '', $name); + } + } + + if($channel_id) { + $m = menu_fetch($name,$channel_id, get_observer_hash()); + return menu_render($m, $class, $edit = false, $var); + } + } + + + function replace_region($match) { + if (array_key_exists($match[1], \App::$page)) { + return \App::$page[$match[1]]; + } + } + + /** + * @brief Returns the channel_id of the profile owner of the page. + * + * Returns the channel_id of the profile owner of the page, or the local_channel + * if there is no profile owner. Otherwise returns 0. + * + * @return channel_id + */ + + function get_channel_id() { + $channel_id = ((is_array(\App::$profile)) ? \App::$profile['profile_uid'] : 0); + + if ((! $channel_id) && (local_channel())) + $channel_id = local_channel(); + + return $channel_id; + } + + function block($s, $class = '') { + $var = array(); + $matches = array(); + $name = $s; + $class = (($class) ? $class : 'bblock widget'); + + $cnt = preg_match_all("/\[var=(.*?)\](.*?)\[\/var\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $var[$mtch[1]] = $mtch[2]; + $name = str_replace($mtch[0], '', $name); + } + } + + $o = ''; + $channel_id = $this->get_channel_id(); + + if($channel_id) { + $r = q("select * from item inner join iconfig on iconfig.iid = item.id and item.uid = %d + and iconfig.cat = 'system' and iconfig.k = 'BUILDBLOCK' and iconfig.v = '%s' limit 1", + intval($channel_id), + dbesc($name) + ); + + if($r) { + //check for eventual menus in the block and parse them + $cnt = preg_match_all("/\[menu\](.*?)\[\/menu\]/ism", $r[0]['body'], $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $r[0]['body'] = str_replace($mtch[0], $this->menu(trim($mtch[1])), $r[0]['body']); + } + } + $cnt = preg_match_all("/\[menu=(.*?)\](.*?)\[\/menu\]/ism", $r[0]['body'], $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $r[0]['body'] = str_replace($mtch[0],$this->menu(trim($mtch[2]),$mtch[1]),$r[0]['body']); + } + } + + //emit the block + $o .= (($var['wrap'] == 'none') ? '' : '
    '); + + if($r[0]['title'] && trim($r[0]['body']) != '$content') { + $o .= '

    ' . $r[0]['title'] . '

    '; + } + + if(trim($r[0]['body']) === '$content') { + $o .= \App::$page['content']; + } + else { + $o .= prepare_text($r[0]['body'], $r[0]['mimetype']); + } + + $o .= (($var['wrap'] == 'none') ? '' : '
    '); + } + } + + return $o; + } + + function js($s) { + + switch($s) { + case 'jquery': + $path = 'view/js/jquery.js'; + break; + case 'bootstrap': + $path = 'library/bootstrap/js/bootstrap.min.js'; + break; + case 'foundation': + $path = 'library/foundation/js/foundation.min.js'; + $init = "\r\n" . ''; + break; + } + + $ret = ''; + if($init) + $ret .= $init; + + return $ret; + + } + + function css($s) { + + switch($s) { + case 'bootstrap': + $path = 'library/bootstrap/css/bootstrap.min.css'; + break; + case 'foundation': + $path = 'library/foundation/css/foundation.min.css'; + break; + } + + $ret = ''; + + return $ret; + + } + + // This doesn't really belong in Comanche, but it could also be argued that it is the perfect place. + // We need to be able to select what kind of template and decoration to use for the webpage at the heart of our content. + // For now we'll allow an '[authored]' element which defaults to name and date, or 'none' to remove these, and perhaps + // 'full' to provide a social network style profile photo. + // But leave it open to have richer templating options and perhaps ultimately discard this one, once we have a better idea + // of what template and webpage options we might desire. + + function webpage(&$a,$s) { + $ret = array(); + $matches = array(); + + $cnt = preg_match_all("/\[authored\](.*?)\[\/authored\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $ret['authored'] = $mtch[1]; + } + } + return $ret; + } + + + /** + * Render a widget + * + * @param string $name + * @param string $text + */ + + function widget($name, $text) { + $vars = array(); + $matches = array(); + + + $cnt = preg_match_all("/\[var=(.*?)\](.*?)\[\/var\]/ism", $text, $matches, PREG_SET_ORDER); + if ($cnt) { + foreach ($matches as $mtch) { + $vars[$mtch[1]] = $mtch[2]; + } + } + + $func = 'widget_' . trim($name); + + if(! function_exists($func)) { + if(file_exists('widget/' . trim($name) . '.php')) + require_once('widget/' . trim($name) . '.php'); + elseif(file_exists('widget/' . trim($name) . '/' . trim($name) . '.php')) + require_once('widget/' . trim($name) . '/' . trim($name) . '.php'); + } + else { + $theme_widget = $func . '.php'; + if((! function_exists($func)) && theme_include($theme_widget)) + require_once(theme_include($theme_widget)); + } + + if(function_exists($func)) + return $func($vars); + } + + + function region($s,$region_name) { + + $s = str_replace('$region',$region_name,$s); + + $matches = array(); + + $cnt = preg_match_all("/\[menu\](.*?)\[\/menu\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0], $this->menu(trim($mtch[1])), $s); + } + } + + // menu class e.g. [menu=horizontal]my_menu[/menu] or [menu=tabbed]my_menu[/menu] + // allows different menu renderings to be applied + + $cnt = preg_match_all("/\[menu=(.*?)\](.*?)\[\/menu\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],$this->menu(trim($mtch[2]),$mtch[1]),$s); + } + } + $cnt = preg_match_all("/\[block\](.*?)\[\/block\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],$this->block(trim($mtch[1])),$s); + } + } + + $cnt = preg_match_all("/\[block=(.*?)\](.*?)\[\/block\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],$this->block(trim($mtch[2]),trim($mtch[1])),$s); + } + } + + $cnt = preg_match_all("/\[js\](.*?)\[\/js\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],$this->js(trim($mtch[1])),$s); + } + } + + $cnt = preg_match_all("/\[css\](.*?)\[\/css\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],$this->css(trim($mtch[1])),$s); + } + } + // need to modify this to accept parameters + + $cnt = preg_match_all("/\[widget=(.*?)\](.*?)\[\/widget\]/ism", $s, $matches, PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0],$this->widget(trim($mtch[1]),$mtch[2]),$s); + } + } + + return $s; + } + + + /* + * @function register_page_template($arr) + * Registers a page template/variant for use by Comanche selectors + * @param array $arr + * 'template' => template name + * 'variant' => array( + * 'name' => variant name + * 'desc' => text description + * 'regions' => array( + * 'name' => name + * 'desc' => text description + * ) + * ) + */ + + + function register_page_template($arr) { + \App::$page_layouts[$arr['template']] = array($arr['variant']); + return; + } + +} \ No newline at end of file diff --git a/Zotlabs/Render/SimpleTemplate.php b/Zotlabs/Render/SimpleTemplate.php new file mode 100755 index 000000000..ff1bb5c3c --- /dev/null +++ b/Zotlabs/Render/SimpleTemplate.php @@ -0,0 +1,310 @@ + 5.3, not certain how to code around it for unit tests +// case PREG_BAD_UTF8_OFFSET_ERROR: echo('PREG_BAD_UTF8_OFFSET_ERROR'); break; + default: + //die("Unknown preg error."); + return; + } + echo "
    ";
    +		debug_print_backtrace();
    +		die();
    +	}
    +
    +	private function _push_stack() {
    +		$this->stack[] = array($this->r, $this->nodes);
    +	}
    +
    +	private function _pop_stack(){
    +		list($this->r, $this->nodes) = array_pop($this->stack);
    +	}
    +
    +	private function _get_var($name, $retNoKey=false) {
    +		$keys = array_map('trim',explode(".",$name));
    +		if ($retNoKey && !array_key_exists($keys[0], $this->r))
    +			return KEY_NOT_EXISTS;
    +
    +		$val = $this->r;
    +		foreach($keys as $k) {
    +			$val = (isset($val[$k]) ? $val[$k] : null);
    +		}
    +
    +		return template_escape($val);
    +	}
    +
    +	/**
    +	 * IF node
    +	 * \code
    +	 * {{ if <$var> }}...[{{ else }} ...] {{ endif }}
    +	 * {{ if <$var>== }}...[{{ else }} ...]{{ endif }}
    +	 * {{ if <$var>!= }}...[{{ else }} ...]{{ endif }}
    +	 * \endcode
    +	 */
    +	private function _replcb_if($args) {
    +		if (strpos($args[2],"==")>0){
    +			list($a,$b) = array_map("trim",explode("==",$args[2]));
    +			$a = $this->_get_var($a);
    +			if ($b[0]=="$") $b =  $this->_get_var($b);
    +			$val = ($a == $b);
    +		} else if (strpos($args[2],"!=")>0){
    +			list($a,$b) = array_map("trim", explode("!=",$args[2]));
    +			$a = $this->_get_var($a);
    +			if ($b[0]=="$") $b =  $this->_get_var($b);
    +			$val = ($a != $b);
    +		} else {
    +			$val = $this->_get_var($args[2]);
    +		}
    +		$x = preg_split("|{{ *else *}}|", $args[3]);
    +
    +		return ( ($val) ? $x[0] : (isset($x[1]) ? $x[1] : ""));
    +	}
    +
    +	/**
    +	 * FOR node
    +	 * \code
    +	 * {{ for <$var> as $name }}...{{ endfor }}
    +	 * {{ for <$var> as $key=>$name }}...{{ endfor }}
    +	 * \endcode
    +	 */
    +	private function _replcb_for($args) {
    +		$m = array_map('trim', explode(" as ", $args[2]));
    +		$x = explode("=>",$m[1]);
    +		if (count($x) == 1) {
    +			$varname = $x[0];
    +			$keyname = "";
    +		} else {
    +			list($keyname, $varname) = $x;
    +		}
    +		if ($m[0]=="" || $varname=="" || is_null($varname)) die("template error: 'for ".$m[0]." as ".$varname."'") ;
    +		//$vals = $this->r[$m[0]];
    +		$vals = $this->_get_var($m[0]);
    +		$ret="";
    +		if (!is_array($vals)) return $ret;
    +
    +		foreach ($vals as $k=>$v){
    +			$this->_push_stack();
    +			$r = $this->r;
    +			$r[$varname] = $v;
    +			if ($keyname!='') $r[$keyname] = (($k === 0) ? '0' : $k);
    +			$ret .=  $this->replace($args[3], $r);
    +			$this->_pop_stack();
    +		}
    +
    +		return $ret;
    +	}
    +
    +	/**
    +	 * INC node
    +	 * \code
    +	 * {{ inc  [with $var1=$var2] }}{{ endinc }}
    +	 * \endcode
    +	 */
    +	private function _replcb_inc($args) {
    +		if (strpos($args[2],"with")) {
    +			list($tplfile, $newctx) = array_map('trim', explode("with",$args[2]));
    +		} else {
    +			$tplfile = trim($args[2]);
    +			$newctx = null;
    +		}
    +
    +		if ($tplfile[0]=="$") $tplfile = $this->_get_var($tplfile);
    +
    +		$this->_push_stack();
    +		$r = $this->r;
    +		if (!is_null($newctx)) {
    +			list($a,$b) = array_map('trim', explode("=",$newctx));
    +			$r[$a] = $this->_get_var($b); 
    +		}
    +		$this->nodes = Array();
    +		$tpl = get_markup_template($tplfile);
    +		$ret = $this->replace($tpl, $r);
    +		$this->_pop_stack();
    +
    +		return $ret;
    +	}
    +
    +	/**
    +	 * DEBUG node
    +	 * \code
    +	 * {{ debug $var [$var [$var [...]]] }}{{ enddebug }}
    +	 * \endcode
    +	 * replace node with 
    var_dump($var, $var, ...);
    + */ + private function _replcb_debug($args) { + $vars = array_map('trim', explode(" ",$args[2])); + $vars[] = $args[1]; + + $ret = "
    ";
    +		foreach ($vars as $var){
    +			$ret .= htmlspecialchars(var_export( $this->_get_var($var), true ));
    +			$ret .= "\n";
    +		}
    +		$ret .= "
    "; + + return $ret; + } + + private function _replcb_node($m) { + $node = $this->nodes[$m[1]]; + if (method_exists($this, "_replcb_".$node[1])){ + $s = call_user_func(array($this, "_replcb_".$node[1]), $node); + } else { + $s = ""; + } + $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s); + + return $s; + } + + private function _replcb($m) { + //var_dump(array_map('htmlspecialchars', $m)); + $this->done = false; + $this->nodes[] = (array) $m; + + return "||". (count($this->nodes)-1) ."||"; + } + + private function _build_nodes($s) { + $this->done = false; + while (!$this->done) { + $this->done=true; + $s = preg_replace_callback('|{{ *([a-z]*) *([^}]*)}}([^{]*({{ *else *}}[^{]*)?){{ *end\1 *}}|', array($this, "_replcb"), $s); + if ($s==Null) $this->_preg_error(); + } + //({{ *else *}}[^{]*)? + krsort($this->nodes); + + return $s; + } + + private function var_replace($s) { + $m = array(); + /** regexp: + * \$ literal $ + * (\[)? optional open square bracket + * ([a-zA-Z0-9-_]+\.?)+ var name, followed by optional + * dot, repeated at least 1 time + * (?(1)\]) if there was opened square bracket + * (subgrup 1), match close bracket + */ + if (preg_match_all('/\$(\[)?([a-zA-Z0-9-_]+\.?)+(?(1)\])/', $s,$m)) { + foreach ($m[0] as $var) { + $exp = str_replace(array("[", "]"), array("", ""), $var); + $exptks = explode("|", $exp); + + $varn = $exptks[0]; + unset($exptks[0]); + $val = $this->_get_var($varn, true); + if ($val != KEY_NOT_EXISTS) { + /* run filters */ + /* + * Filter are in form of: + * filtername:arg:arg:arg + * + * "filtername" is function name + * "arg"s are optional, var value is appended to the end + * if one "arg"==='x' , is replaced with var value + * + * examples: + * $item.body|htmlspecialchars // escape html chars + * $item.body|htmlspecialchars|strtoupper // escape html and uppercase result + * $item.created|date:%Y %M %j // format date (created is a timestamp) + * $item.body|str_replace:cat:dog // replace all "cat" with "dog" + * $item.body|str_replace:cat:dog:x:1 // replace one "cat" with "dog" + */ + foreach ($exptks as $filterstr) { + $filter = explode(":", $filterstr); + $filtername = $filter[0]; + unset($filter[0]); + $valkey = array_search("x", $filter); + if ($valkey === false) { + $filter[] = $val; + } else { + $filter[$valkey] = $val; + } + if (function_exists($filtername)) { + $val = call_user_func_array($filtername, $filter); + } + } + $s = str_replace($var, $val, $s); + } + } + } + + return $s; + } + + private function replace($s, $r) { + $this->replace_macros($s, $r); + } + + // TemplateEngine interface + + public function replace_macros($s, $r) { + $this->r = $r; + + $s = $this->_build_nodes($s); + + $s = preg_replace_callback('/\|\|([0-9]+)\|\|/', array($this, "_replcb_node"), $s); + if ($s == Null) + $this->_preg_error(); + + // remove comments block + $s = preg_replace('/{#[^#]*#}/', "" , $s); + + //$t2 = dba_timer(); + + // replace strings recursively (limit to 10 loops) + $os = ""; + $count=0; + while (($os !== $s) && $count<10) { + $os=$s; + $count++; + $s = $this->var_replace($s); + } + + return $s; + } + + public function get_markup_template($file, $root='') { + $template_file = theme_include($file, $root); + if ($template_file) { + $content = file_get_contents($template_file); + } + + return $content; + } +} + + +function template_escape($s) { + return str_replace(array('$','{{'),array('!_Doll^Ars1Az_!','!_DoubLe^BraceS4Rw_!'),$s); +} + +function template_unescape($s) { + return str_replace(array('!_Doll^Ars1Az_!','!_DoubLe^BraceS4Rw_!'),array('$','{{'),$s); +} diff --git a/Zotlabs/Render/SmartyInterface.php b/Zotlabs/Render/SmartyInterface.php new file mode 100755 index 000000000..0e3a47c2f --- /dev/null +++ b/Zotlabs/Render/SmartyInterface.php @@ -0,0 +1,48 @@ + "view/theme/$thname/tpl/"); + if( x(\App::$theme_info,"extends") ) + $template_dirs = $template_dirs + array('extends' => "view/theme/" . \App::$theme_info["extends"] . "/tpl/"); + $template_dirs = $template_dirs + array('base' => 'view/tpl/'); + $this->setTemplateDir($template_dirs); + + $basecompiledir = \App::$config['system']['smarty3_folder']; + + $this->setCompileDir($basecompiledir.'/compiled/'); + $this->setConfigDir($basecompiledir.'/config/'); + $this->setCacheDir($basecompiledir.'/cache/'); + + $this->left_delimiter = \App::get_template_ldelim('smarty3'); + $this->right_delimiter = \App::get_template_rdelim('smarty3'); + + // Don't report errors so verbosely + $this->error_reporting = E_ALL & (~E_NOTICE); + } + + function parsed($template = '') { + if($template) { + return $this->fetch('string:' . $template); + } + return $this->fetch('file:' . $this->filename); + } +} + + + diff --git a/Zotlabs/Render/SmartyTemplate.php b/Zotlabs/Render/SmartyTemplate.php new file mode 100755 index 000000000..532d6e42f --- /dev/null +++ b/Zotlabs/Render/SmartyTemplate.php @@ -0,0 +1,75 @@ +ERROR: folder $basecompiledir does not exist."; killme(); + } + if(!is_writable($basecompiledir)){ + echo "ERROR: folder $basecompiledir must be writable by webserver."; killme(); + } + \App::$config['system']['smarty3_folder'] = $basecompiledir; + } + + // TemplateEngine interface + + public function replace_macros($s, $r) { + $template = ''; + if(gettype($s) === 'string') { + $template = $s; + $s = new SmartyInterface(); + } + foreach($r as $key=>$value) { + if($key[0] === '$') { + $key = substr($key, 1); + } + $s->assign($key, $value); + } + return $s->parsed($template); + } + + public function get_markup_template($file, $root=''){ + $template_file = theme_include($file, $root); + if($template_file) { + $template = new SmartyInterface(); + $template->filename = $template_file; + + return $template; + } + return ""; + } + + public function get_intltext_template($file, $root='') { + + $lang = \App::$language; + + if(file_exists("view/$lang/$file")) + $template_file = "view/$lang/$file"; + elseif(file_exists("view/en/$file")) + $template_file = "view/en/$file"; + else + $template_file = theme_include($file,$root); + if($template_file) { + $template = new SmartyInterface(); + $template->filename = $template_file; + + return $template; + } + return ""; + } + + + +} diff --git a/Zotlabs/Render/TemplateEngine.php b/Zotlabs/Render/TemplateEngine.php new file mode 100755 index 000000000..600ff913e --- /dev/null +++ b/Zotlabs/Render/TemplateEngine.php @@ -0,0 +1,12 @@ + 1) ? $t[1] : ''); + + $opts = ''; + $opts = ((\App::$profile_uid) ? '?f=&puid=' . \App::$profile_uid : ''); + + $schema_str = ((x(\App::$layout,'schema')) ? '&schema=' . App::$layout['schema'] : ''); + if(($s) && (! $schema_str)) + $schema_str = '&schema=' . $s; + $opts .= $schema_str; + + if(file_exists('view/theme/' . $t . '/php/style.php')) + return('view/theme/' . $t . '/php/style.pcss' . $opts); + + return('view/theme/' . $t . '/css/style.css'); + } +} + diff --git a/Zotlabs/Storage/BasicAuth.php b/Zotlabs/Storage/BasicAuth.php index 637cd222f..995976dcd 100644 --- a/Zotlabs/Storage/BasicAuth.php +++ b/Zotlabs/Storage/BasicAuth.php @@ -3,6 +3,8 @@ namespace Zotlabs\Storage; use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; /** * @brief Authentication backend class for DAV. @@ -73,10 +75,12 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { protected $timezone = ''; + public $module_disabled = false; + + /** * @brief Validates a username and password. * - * Guest access is granted with the password "+++". * * @see \Sabre\DAV\Auth\Backend\AbstractBasic::validateUserPass * @param string $username @@ -84,42 +88,29 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { * @return bool */ protected function validateUserPass($username, $password) { - if (trim($password) === '+++') { - logger('guest: ' . $username); - return true; - } require_once('include/auth.php'); $record = account_verify_password($username, $password); - if ($record && $record['account_default_channel']) { - $r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1", - intval($record['account_id']), - intval($record['account_default_channel']) - ); - if ($r) { - return $this->setAuthenticated($r[0]); + if($record && $record['account']) { + if($record['channel']) + $channel = $record['channel']; + else { + $r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1", + intval($record['account']['account_id']), + intval($record['account']['account_default_channel']) + ); + if($r) + $channel = $r[0]; } } - $r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1", - dbesc($username) - ); - if ($r) { - $x = q("SELECT account_flags, account_salt, account_password FROM account WHERE account_id = %d LIMIT 1", - intval($r[0]['channel_account_id']) - ); - if ($x) { - // @fixme this foreach should not be needed? - foreach ($x as $record) { - if ((($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED)) - && (hash('whirlpool', $record['account_salt'] . $password) === $record['account_password'])) { - logger('password verified for ' . $username); - return $this->setAuthenticated($r[0]); - } - } - } + if($channel && $this->check_module_access($channel['channel_id'])) { + return $this->setAuthenticated($channel); } - $error = 'password failed for ' . $username; + if($this->module_disabled) + $error = 'module not enabled for ' . $username; + else + $error = 'password failed for ' . $username; logger($error); log_failed_login($error); @@ -143,6 +134,69 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { return true; } + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + if(local_channel()) { + $this->setAuthenticated(\App::get_channel()); + return [ true, $this->principalPrefix . $this->channel_name ]; + } + + $auth = new \Sabre\HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + + $userpass = $auth->getCredentials(); + if (!$userpass) { + return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"]; + } + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + return [false, "Username or password was incorrect"]; + } + return [true, $this->principalPrefix . $userpass[0]]; + + } + + protected function check_module_access($channel_id) { + if($channel_id && \App::$module === 'cdav') { + $x = get_pconfig($channel_id,'cdav','enabled'); + if(! $x) { + $this->module_disabled = true; + return false; + } + } + return true; + } + /** * Sets the channel_name from the currently logged-in channel. * @@ -165,7 +219,7 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { } /** - * @brief Sets the timezone from the channel in RedBasicAuth. + * @brief Sets the timezone from the channel in BasicAuth. * * Set in mod/cloud.php if the channel has a timezone set. * @@ -209,4 +263,4 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { logger('owner_id ' . $this->owner_id, LOGGER_DATA); logger('owner_nick ' . $this->owner_nick, LOGGER_DATA); } -} \ No newline at end of file +} diff --git a/Zotlabs/Storage/Browser.php b/Zotlabs/Storage/Browser.php index 720940953..713d75108 100644 --- a/Zotlabs/Storage/Browser.php +++ b/Zotlabs/Storage/Browser.php @@ -101,8 +101,8 @@ class Browser extends DAV\Browser\Plugin { $parentpath = array(); // only show parent if not leaving /cloud/; TODO how to improve this? if ($path && $path != "cloud") { - list($parentUri) = DAV\URLUtil::splitPath($path); - $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); + list($parentUri) = \Sabre\HTTP\URLUtil::splitPath($path); + $fullPath = \Sabre\HTTP\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); $parentpath['icon'] = $this->enableAssets ? '' . t('parent') . '' : ''; $parentpath['path'] = $fullPath; @@ -116,7 +116,7 @@ class Browser extends DAV\Browser\Plugin { // This is the current directory, we can skip it if (rtrim($file['href'],'/') == $path) continue; - list(, $name) = DAV\URLUtil::splitPath($file['href']); + list(, $name) = \Sabre\HTTP\URLUtil::splitPath($file['href']); if (isset($file[200]['{DAV:}resourcetype'])) { $type = $file[200]['{DAV:}resourcetype']->getValue(); @@ -166,7 +166,7 @@ class Browser extends DAV\Browser\Plugin { $size = isset($file[200]['{DAV:}getcontentlength']) ? (int)$file[200]['{DAV:}getcontentlength'] : ''; $lastmodified = ((isset($file[200]['{DAV:}getlastmodified'])) ? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') : ''); - $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/')); + $fullPath = \Sabre\HTTP\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/')); $displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name; @@ -197,7 +197,7 @@ class Browser extends DAV\Browser\Plugin { } } - $attachIcon = ""; // ""; + $attachIcon = ""; // ""; // put the array for this file together $ft['attachId'] = $this->findAttachIdByHash($attachHash); @@ -219,7 +219,7 @@ class Browser extends DAV\Browser\Plugin { $output = ''; if ($this->enablePost) { - $this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output)); + $this->server->emit('onHTMLActionsPanel', array($parent, &$output, $path)); } $html .= replace_macros(get_markup_template('cloud.tpl'), array( @@ -246,14 +246,17 @@ class Browser extends DAV\Browser\Plugin { \App::$page['content'] = $html; load_pdl($a); - $theme_info_file = "view/theme/" . current_theme() . "/php/theme.php"; + $current_theme = \Zotlabs\Render\Theme::current(); + + $theme_info_file = "view/theme/" . $current_theme[0] . "/php/theme.php"; if (file_exists($theme_info_file)){ require_once($theme_info_file); - if (function_exists(str_replace('-', '_', current_theme()) . '_init')) { - $func = str_replace('-', '_', current_theme()) . '_init'; + if (function_exists(str_replace('-', '_', $current_theme[0]) . '_init')) { + $func = str_replace('-', '_', $current_theme[0]) . '_init'; $func($a); } } + $this->server->httpResponse->setHeader('Content-Security-Policy', "script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"); construct_page($a); } @@ -263,7 +266,7 @@ class Browser extends DAV\Browser\Plugin { * @param \Sabre\DAV\INode $node * @param string &$output */ - public function htmlActionsPanel(DAV\INode $node, &$output) { + public function htmlActionsPanel(DAV\INode $node, &$output, $path) { if (! $node instanceof DAV\ICollection) return; @@ -273,7 +276,7 @@ class Browser extends DAV\Browser\Plugin { return; // Storage and quota for the account (all channels of the owner of this directory)! - $limit = service_class_fetch($owner, 'attach_upload_limit'); + $limit = engr_units_to_bytes(service_class_fetch($owner, 'attach_upload_limit')); $r = q("SELECT SUM(filesize) AS total FROM attach WHERE aid = %d", intval($this->auth->channel_account_id) ); diff --git a/Zotlabs/Storage/CalDAVClient.php b/Zotlabs/Storage/CalDAVClient.php new file mode 100644 index 000000000..c1a8db932 --- /dev/null +++ b/Zotlabs/Storage/CalDAVClient.php @@ -0,0 +1,738 @@ +username = $user; + $this->password = $pass; + $this->url = $url; + + } + + private function set_data($s) { + $this->request_data = $s; + $this->filepos = 0; + } + + public function curl_read($ch,$fh,$size) { + + if($this->filepos < 0) { + unset($fh); + return ''; + } + + $s = substr($this->request_data,$this->filepos,$size); + + if(strlen($s) < $size) + $this->filepos = (-1); + else + $this->filepos = $this->filepos + $size; + + return $s; + } + + function ctag_fetch() { + $headers = [ 'Depth: 0', 'Prefer: return-minimal', 'Content-Type: application/xml; charset=utf-8']; + + // recommended ctag fetch by sabre + + $this->set_data(' + + + + + + +'); + + // thunderbird uses this - it's a bit more verbose on what capabilities + // are provided by the server + + $this->set_data(' + + + + + + + + + +'); + + + + $auth = $this->username . ':' . $this->password; + + $recurse = 0; + + $x = z_fetch_url($this->url,true,$recurse, + [ 'headers' => $headers, + 'http_auth' => $auth, + 'custom' => 'PROPFIND', + 'upload' => true, + 'infile' => 3, + 'infilesize' => strlen($this->request_data), + 'readfunc' => [ $this, 'curl_read' ] + ]); + + return $x; + + } + + + function detail_fetch() { + $headers = [ 'Depth: 1', 'Prefer: return-minimal', 'Content-Type: application/xml; charset=utf-8']; + + // this query should return all objects in the given calendar, you can filter it appropriately + // using filter options + + $this->set_data(' + + + + + + + + +'); + + $auth = $this->username . ':' . $this->password; + + $recurse = 0; + $x = z_fetch_url($this->url,true,$recurse, + [ 'headers' => $headers, + 'http_auth' => $auth, + 'custom' => 'REPORT', + 'upload' => true, + 'infile' => 3, + 'infilesize' => strlen($this->request_data), + 'readfunc' => [ $this, 'curl_read' ] + ]); + + + return $x; + + } + + +} + + + +/* + +PROPFIND /calendars/johndoe/home/ HTTP/1.1 +Depth: 0 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + +// Responses: success + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/ + + + Home calendar + 3145 + + HTTP/1.1 200 OK + + + + +// Responses: fail + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/ + + + + + + HTTP/1.1 403 Forbidden + + + + + +// sample request body in DOM +// prepare request body +$doc = new DOMDocument('1.0', 'utf-8'); +$doc->formatOutput = true; + +$query = $doc->createElement('c:calendar-query'); +$query->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:c', 'urn:ietf:params:xml:ns:caldav'); +$query->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:d', 'DAV:'); + +$prop = $doc->createElement('d:prop'); +$prop->appendChild($doc->createElement('d:getetag')); +$prop->appendChild($doc->createElement('c:calendar-data')); +$query->appendChild($prop); +$doc->appendChild($query); +$body = $doc->saveXML(); + +echo "Body: " . $body . "
    "; + + +Now we download every single object in this calendar. To do this, we use a REPORT method. + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + + +This request will give us every object that's a VCALENDAR object, and its etag. + +If you're only interested in VTODO (because you're writing a todo app) you can also filter for just those: + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + + + + +Similarly it's also possible to filter to just events, or only get events within a specific time-range. + +This report will return a multi-status object again: + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/132456762153245.ics + + + "2134-314" + BEGIN:VCALENDAR + VERSION:2.0 + CALSCALE:GREGORIAN + BEGIN:VTODO + UID:132456762153245 + SUMMARY:Do the dishes + DUE:20121028T115600Z + END:VTODO + END:VCALENDAR + + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/132456-34365.ics + + + "5467-323" + BEGIN:VCALENDAR + VERSION:2.0 + CALSCALE:GREGORIAN + BEGIN:VEVENT + UID:132456-34365 + SUMMARY:Weekly meeting + DTSTART:20120101T120000 + DURATION:PT1H + RRULE:FREQ=WEEKLY + END:VEVENT + END:VCALENDAR + + + HTTP/1.1 200 OK + + + + +This calendar only contained 2 objects. A todo and a weekly event. + +So after you retrieved and processed these, for each object you must retain: + + The calendar data itself + The url + The etag + +In this case all urls ended with .ics. This is often the case, but you must not rely on this. In this case the UID in the calendar object was also identical to a part of the url. This too is often the case, but again not something you can rely on, so don't make any assumptions. + +The url and the UID have no meaningful relationship, so treat both those items as separate unique identifiers. +Finding out if anything changed + +To see if anything in a calendar changed, we simply request the ctag again on the calendar. If the ctag did not change, you still have the latest copy. + +If it did change, you must request all the etags in the entire calendar again: + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + + + +Note that this last request is extremely similar to a previous one, but we are only asking fo the etag, not the calendar-data. + +The reason for this, is that calendars can be rather huge. It will save a TON of bandwidth to only check the etag first. + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/home/132456762153245.ics + + + "xxxx-xxx" + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/fancy-caldav-client-1234253678.ics + + + "5-12" + + HTTP/1.1 200 OK + + + + +Judging from this last request, 3 things have changed: + + The etag for the task has changed, so the contents must be different + There's a new url, some other client must have added an object + One object is missing, something must have deleted it. + +So based on those 3 items we know that we need to delete an object from our local list, and fetch the contents for the new item, and the updated one. + +To fetch the data for these, you can simply issue GET requests: + +GET /calendars/johndoe/home/132456762153245.ics HTTP/1.1 + +But, because in a worst-case scenario this could result in a LOT of GET requests we can do a 'multiget'. + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + /calendars/johndoe/home/132456762153245.ics + /calendars/johndoe/home/fancy-caldav-client-1234253678.ics + + +This request will simply return a multi-status again with the calendar-data and etag. +A small note about application design + +If you read this far and understood what's been said, you may have realized that it's a bit cumbersome to have a separate step for the initial sync, and subsequent updates. + +It would totally be possible to skip the 'initial sync', and just use calendar-query and calendar-multiget REPORTS for the initial sync as well. +Updating a calendar object + +Updating a calendar object is rather simple: + +PUT /calendars/johndoe/home/132456762153245.ics HTTP/1.1 +Content-Type: text/calendar; charset=utf-8 +If-Match: "2134-314" + +BEGIN:VCALENDAR +.... +END:VCALENDAR + +A response to this will be something like this: + +HTTP/1.1 204 No Content +ETag: "2134-315" + +The update gave us back the new ETag. SabreDAV gives this ETag on updates back most of the time, but not always. + +There are cases where the caldav server must modify the iCalendar object right after storage. In those cases an ETag will not be returned, and you should issue a GET request immediately to get the correct object. + +A few notes: + + You must not change the UID of the original object + Every object should hold only 1 event or task. + You cannot change an VEVENT into a VTODO. + +Creating a calendar object + +Creating a calendar object is almost identical, except that you don't have a url yet to a calendar object. + +Instead, it is up to you to determine the new url. + +PUT /calendars/johndoe/home/somerandomstring.ics HTTP/1.1 +Content-Type: text/calendar; charset=utf-8 + +BEGIN:VCALENDAR +.... +END:VCALENDAR + +A response to this will be something like this: + +HTTP/1.1 201 Created +ETag: "21345-324" + +Similar to updating, an ETag is often returned, but there are cases where this is not true. +Deleting a calendar object + +Deleting is simple enough: + +DELETE /calendars/johndoe/home/132456762153245.ics HTTP/1.1 +If-Match: "2134-314" + +Speeding up Sync with WebDAV-Sync + +WebDAV-Sync is a protocol extension that is defined in rfc6578. Because this extension was defined later, some servers may not support this yet. + +SabreDAV supports this since 2.0. + +WebDAV-Sync allows a client to ask just for calendars that have changed. The process on a high-level is as follows: + + Client requests sync-token from server. + Server reports token 15. + Some time passes. + Client does a Sync REPORT on an calendar, and supplied token 15. + Server returns vcard urls that have changed or have been deleted and returns token 17. + +As you can see, after the initial sync, only items that have been created, modified or deleted will ever be sent. + +This has a lot of advantages. The transmitted xml bodies can generally be a lot shorter, and is also easier on both client and server in terms of memory and CPU usage, because only a limited set of items will have to be compared. + +It's important to note, that a client should only do Sync operations, if the server reports that it has support for it. The quickest way to do so, is to request {DAV}sync-token on the calendar you wish to sync. + +Technically, a server may support 'sync' on one calendar, and it may not support it on another, although this is probably rare. +Getting the first sync-token + +Initially, we just request a sync token when asking for calendar information: + +PROPFIND /calendars/johndoe/home/ HTTP/1.1 +Depth: 0 +Content-Type: application/xml; charset=utf-8 + + + + + + + + + +This would return something as follows: + + + + /calendars/johndoe/home/ + + + My calendar + 3145 + http://sabredav.org/ns/sync-token/3145 + + HTTP/1.1 200 OK + + + + +As you can see, the sync-token is a url. It always should be a url. Even though a number appears in the url, you are not allowed to attach any meaning to that url. Some servers may have use an increasing number, another server may use a completely random string. +Receiving changes + +After a sync token has been obtained, and the client already has the initial copy of the calendar, the client is able to request all changes since the token was issued. + +This is done with a REPORT request that may look like this: + +REPORT /calendars/johndoe/home/ HTTP/1.1 +Host: dav.example.org +Content-Type: application/xml; charset="utf-8" + + + + http://sabredav.org/ns/sync/3145 + 1 + + + + + +This requests all the changes since sync-token identified by http://sabredav.org/ns/sync/3145, and for the calendar objects that have been added or modified, we're requesting the etag. + +The response to a query like this is another multistatus xml body. Example: + +HTTP/1.1 207 Multi-Status +Content-Type: application/xml; charset="utf-8" + + + + + /calendars/johndoe/home/newevent.ics + + + "33441-34321" + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/updatedevent.ics + + + "33541-34696" + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/deletedevent.ics + HTTP/1.1 404 Not Found + + http://sabredav.org/ns/sync/5001 + + +The last response reported two changes: newevent.ics and updatedevent.ics. There's no way to tell from the response wether those cards got created or updated, you, as a client can only infer this based on the vcards you are already aware of. + +The entry with name deletedevent.ics got deleted as indicated by the 404 status. Note that the status element is here a child of d:response when in all previous examples it has been a child of d:propstat. + +The other difference with the other multi-status examples, is that this one has a sync-token element with the latest sync-token. +Caveats + +Note that a server is free to 'forget' any sync-tokens that have been previously issued. In this case it may be needed to do a full-sync again. + +In case the supplied sync-token is not recognized by the server, a HTTP error is emitted. SabreDAV emits a 403. +Discovery + +Ideally you will want to make sure that all the calendars in an account are automatically discovered. The best user interface would be to just have to ask for three items: + + Username + Password + Server + +And the server should be as short as possible. This is possible with most servers. + +If, for example a user specified 'dav.example.org' for the server, the first thing you should do is attempt to send a PROPFIND request to https://dav.example.org/. Note that you SHOULD try the https url before the http url. + +This PROPFIND request looks as follows: + +PROPFIND / HTTP/1.1 +Depth: 0 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + +This will return a response such as the following: + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + / + + + + /principals/users/johndoe/ + + + HTTP/1.1 200 OK + + + + +A 'principal' is a user. The url that's being returned, is a url that refers to the current user. On this url you can request additional information about the user. + +What we need from this url, is their 'calendar home'. The calendar home is a collection that contains all of the users' calendars. + +To request that, issue the following request: + +PROPFIND /principals/users/johndoe/ HTTP/1.1 +Depth: 0 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + +This will return a response such as the following: + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /principals/users/johndoe/ + + + + /calendars/johndoe/ + + + HTTP/1.1 200 OK + + + + +Lastly, to list all the calendars for the user, issue a PROPFIND request with Depth: 1. + +PROPFIND /calendars/johndoe/ HTTP/1.1 +Depth: 1 +Prefer: return-minimal +Content-Type: application/xml; charset=utf-8 + + + + + + + + + + +In that last request, we asked for 4 properties. + +The resourcetype tells us what type of object we're getting back. You must read out the resourcetype and ensure that it contains at least a calendar element in the CalDAV namespace. Other items may be returned, including non- calendar, which your application should ignore. + +The displayname is a human-readable string for the calendarname, the ctag was already covered in an earlier section. + +Lastly, supported-calendar-component-set. This gives us a list of components that the calendar accepts. This could be just VTODO, VEVENT, VJOURNAL or a combination of these three. + +If you are just creating a todo-list application, this means you should only list the calendars that support the VTODO component. + +HTTP/1.1 207 Multi-status +Content-Type: application/xml; charset=utf-8 + + + + /calendars/johndoe/ + + + + + + + HTTP/1.1 200 OK + + + + /calendars/johndoe/home/ + + + + + + + Home calendar + 3145 + + + + + HTTP/1.1 200 OK + + + + /calendars/johndoe/tasks/ + + + + + + + My TODO list + 3345 + + + + + HTTP/1.1 200 OK + + + +*/ diff --git a/Zotlabs/Storage/Directory.php b/Zotlabs/Storage/Directory.php index edbef5a95..6242d5274 100644 --- a/Zotlabs/Storage/Directory.php +++ b/Zotlabs/Storage/Directory.php @@ -91,7 +91,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { throw new DAV\Exception\Forbidden('Permission denied.'); } - $contents = RedCollectionData($this->red_path, $this->auth); + $contents = $this->CollectionData($this->red_path, $this->auth); return $contents; } @@ -119,7 +119,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { return new Directory('/' . $modulename, $this->auth); } - $x = RedFileData($this->ext_path . '/' . $name, $this->auth); + $x = $this->FileData($this->ext_path . '/' . $name, $this->auth); if ($x) { return $x; } @@ -194,7 +194,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { * @return null|string ETag */ public function createFile($name, $data = null) { - logger($name, LOGGER_DEBUG); + logger('create file in directory ' . $name, LOGGER_DEBUG); if (! $this->auth->owner_id) { logger('permission denied ' . $name); @@ -206,6 +206,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { throw new DAV\Exception\Forbidden('Permission denied.'); } + $mimetype = z_mime_content_type($name); $c = q("SELECT * FROM channel WHERE channel_id = %d AND channel_removed = 0 LIMIT 1", @@ -246,7 +247,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { $deny_gid = $c[0]['channel_deny_gid']; } - $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, folder, os_storage, filetype, filesize, revision, is_photo, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, folder, os_storage, filetype, filesize, revision, is_photo, content, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", intval($c[0]['channel_account_id']), intval($c[0]['channel_id']), @@ -315,13 +316,13 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { } // check against service class quota - $limit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit'); + $limit = engr_units_to_bytes(service_class_fetch($c[0]['channel_id'], 'attach_upload_limit')); if ($limit !== false) { $x = q("SELECT SUM(filesize) AS total FROM attach WHERE aid = %d ", intval($c[0]['channel_account_id']) ); if (($x) && ($x[0]['total'] + $size > $limit)) { - logger('service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); + logger('service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . userReadableSize($limit)); attach_delete($c[0]['channel_id'], $hash); return; } @@ -358,7 +359,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { * @return void */ public function createDirectory($name) { - logger($name, LOGGER_DEBUG); + logger('create directory ' . $name, LOGGER_DEBUG); if ((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'write_storage'))) { throw new DAV\Exception\Forbidden('Permission denied.'); @@ -372,7 +373,9 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { $result = attach_mkdir($r[0], $this->auth->observer, array('filename' => $name, 'folder' => $this->folder_hash)); if($result['success']) { - $sync = attach_export_data($r[0],$ret['data']['hash']); + $sync = attach_export_data($r[0],$result['data']['hash']); + logger('createDirectory: attach_export_data returns $sync:' . print_r($sync, true), LOGGER_DEBUG); + if($sync) { build_sync_packet($r[0]['channel_id'],array('file' => array($sync))); } @@ -429,8 +432,8 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { return true; } - $x = RedFileData($this->ext_path . '/' . $name, $this->auth, true); - //logger('RedFileData returns: ' . print_r($x, true), LOGGER_DATA); + $x = $this->FileData($this->ext_path . '/' . $name, $this->auth, true); + //logger('FileData returns: ' . print_r($x, true), LOGGER_DATA); if ($x) return true; @@ -549,7 +552,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { intval($this->auth->owner_id) ); - $ulimit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit'); + $ulimit = engr_units_to_bytes(service_class_fetch($c[0]['channel_id'], 'attach_upload_limit')); $limit = (($ulimit) ? $ulimit : $limit); $x = q("select sum(filesize) as total from attach where aid = %d", @@ -563,4 +566,280 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota { $free ); } -} \ No newline at end of file + + + /** + * @brief Array with all Directory and File DAV\Node items for the given path. + * + * + * @param string $file path to a directory + * @param \Zotlabs\Storage\BasicAuth &$auth + * @returns null|array \Sabre\DAV\INode[] + * @throw \Sabre\DAV\Exception\Forbidden + * @throw \Sabre\DAV\Exception\NotFound + */ + + function CollectionData($file, &$auth) { + $ret = array(); + + $x = strpos($file, '/cloud'); + if ($x === 0) { + $file = substr($file, 6); + } + + // return a list of channel if we are not inside a channel + if ((! $file) || ($file === '/')) { + return $this->ChannelList($auth); + } + + $file = trim($file, '/'); + $path_arr = explode('/', $file); + + if (! $path_arr) + return null; + + $channel_name = $path_arr[0]; + + $r = q("SELECT channel_id FROM channel WHERE channel_address = '%s' LIMIT 1", + dbesc($channel_name) + ); + + if (! $r) + return null; + + $channel_id = $r[0]['channel_id']; + $perms = permissions_sql($channel_id); + + $auth->owner_id = $channel_id; + + $path = '/' . $channel_name; + + $folder = ''; + $errors = false; + $permission_error = false; + + for ($x = 1; $x < count($path_arr); $x++) { + $r = q("SELECT id, hash, filename, flags, is_dir FROM attach WHERE folder = '%s' AND filename = '%s' AND uid = %d AND is_dir != 0 $perms LIMIT 1", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id) + ); + if (! $r) { + // path wasn't found. Try without permissions to see if it was the result of permissions. + $errors = true; + $r = q("select id, hash, filename, flags, is_dir from attach where folder = '%s' and filename = '%s' and uid = %d and is_dir != 0 limit 1", + dbesc($folder), + basename($path_arr[$x]), + intval($channel_id) + ); + if ($r) { + $permission_error = true; + } + break; + } + + if ($r && intval($r[0]['is_dir'])) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + } + + if ($errors) { + if ($permission_error) { + throw new DAV\Exception\Forbidden('Permission denied.'); + } + else { + throw new DAV\Exception\NotFound('A component of the request file path could not be found.'); + } + } + + // This should no longer be needed since we just returned errors for paths not found + if ($path !== '/' . $file) { + logger("Path mismatch: $path !== /$file"); + return NULL; + } + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $prefix = 'DISTINCT ON (filename)'; + $suffix = 'ORDER BY filename'; + } + else { + $prefix = ''; + $suffix = 'GROUP BY filename'; + } + $r = q("select $prefix id, uid, hash, filename, filetype, filesize, revision, folder, flags, is_dir, created, edited from attach where folder = '%s' and uid = %d $perms $suffix", + dbesc($folder), + intval($channel_id) + ); + + foreach ($r as $rr) { + //logger('filename: ' . $rr['filename'], LOGGER_DEBUG); + if (intval($rr['is_dir'])) { + $ret[] = new Directory($path . '/' . $rr['filename'], $auth); + } + else { + $ret[] = new File($path . '/' . $rr['filename'], $rr, $auth); + } + } + + return $ret; + } + + + /** + * @brief Returns an array with viewable channels. + * + * Get a list of Directory objects with all the channels where the visitor + * has view_storage perms. + * + * + * @param BasicAuth &$auth + * @return array Directory[] + */ + + function ChannelList(&$auth) { + $ret = array(); + + $r = q("SELECT channel_id, channel_address FROM channel WHERE channel_removed = 0 + AND channel_system = 0 AND NOT (channel_pageflags & %d)>0", + intval(PAGE_HIDDEN) + ); + + if ($r) { + foreach ($r as $rr) { + if (perm_is_allowed($rr['channel_id'], $auth->observer, 'view_storage')) { + logger('found channel: /cloud/' . $rr['channel_address'], LOGGER_DATA); + // @todo can't we drop '/cloud'? It gets stripped off anyway in RedDirectory + $ret[] = new Directory('/cloud/' . $rr['channel_address'], $auth); + } + } + } + return $ret; + } + + + /** + * @brief + * + * + * @param string $file + * path to file or directory + * @param BasicAuth &$auth + * @param boolean $test (optional) enable test mode + * @return File|Directory|boolean|null + * @throw \Sabre\DAV\Exception\Forbidden + */ + + function FileData($file, &$auth, $test = false) { + logger($file . (($test) ? ' (test mode) ' : ''), LOGGER_DATA); + + $x = strpos($file, '/cloud'); + if ($x === 0) { + $file = substr($file, 6); + } + else { + $x = strpos($file,'/dav'); + if($x === 0) + $file = substr($file,4); + } + + + if ((! $file) || ($file === '/')) { + return new Directory('/', $auth); + } + + $file = trim($file, '/'); + + $path_arr = explode('/', $file); + + if (! $path_arr) + return null; + + $channel_name = $path_arr[0]; + + $r = q("select channel_id from channel where channel_address = '%s' limit 1", + dbesc($channel_name) + ); + + if (! $r) + return null; + + $channel_id = $r[0]['channel_id']; + + $path = '/' . $channel_name; + + $auth->owner_id = $channel_id; + + $permission_error = false; + + $folder = ''; + + require_once('include/security.php'); + $perms = permissions_sql($channel_id); + + $errors = false; + + for ($x = 1; $x < count($path_arr); $x++) { + $r = q("select id, hash, filename, flags, is_dir from attach where folder = '%s' and filename = '%s' and uid = %d and is_dir != 0 $perms", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id) + ); + + if ($r && intval($r[0]['is_dir'])) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + if (! $r) { + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, is_dir, os_storage, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d $perms order by filename limit 1", + dbesc($folder), + dbesc(basename($file)), + intval($channel_id) + ); + } + if (! $r) { + $errors = true; + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, is_dir, os_storage, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d order by filename limit 1", + dbesc($folder), + dbesc(basename($file)), + intval($channel_id) + ); + if ($r) + $permission_error = true; + } + } + + if ($path === '/' . $file) { + if ($test) + return true; + // final component was a directory. + return new Directory($file, $auth); + } + + if ($errors) { + logger('not found ' . $file); + if ($test) + return false; + if ($permission_error) { + logger('permission error ' . $file); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + return; + } + + if ($r) { + if ($test) + return true; + + if (intval($r[0]['is_dir'])) { + return new Directory($path . '/' . $r[0]['filename'], $auth); + } + else { + return new File($path . '/' . $r[0]['filename'], $r[0], $auth); + } + } + return false; + } + +} diff --git a/Zotlabs/Storage/File.php b/Zotlabs/Storage/File.php index 897f24edd..5a70a99f1 100644 --- a/Zotlabs/Storage/File.php +++ b/Zotlabs/Storage/File.php @@ -124,7 +124,7 @@ class File extends DAV\Node implements DAV\IFile { ); if ($r) { if (intval($r[0]['os_storage'])) { - $d = q("select folder, data from attach where hash = '%s' and uid = %d limit 1", + $d = q("select folder, content from attach where hash = '%s' and uid = %d limit 1", dbesc($this->data['hash']), intval($c[0]['channel_id']) ); @@ -139,7 +139,7 @@ class File extends DAV\Node implements DAV\IFile { $direct = $f1[0]; } } - $fname = dbunescbin($d[0]['data']); + $fname = dbunescbin($d[0]['content']); if(strpos($fname,'store') === false) $f = 'store/' . $this->auth->owner_nick . '/' . $fname ; else @@ -158,12 +158,12 @@ class File extends DAV\Node implements DAV\IFile { } else { // this shouldn't happen any more - $r = q("UPDATE attach SET data = '%s' WHERE hash = '%s' AND uid = %d", + $r = q("UPDATE attach SET content = '%s' WHERE hash = '%s' AND uid = %d", dbescbin(stream_get_contents($data)), dbesc($this->data['hash']), intval($this->data['uid']) ); - $r = q("SELECT length(data) AS fsize FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", + $r = q("SELECT length(content) AS fsize FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", dbesc($this->data['hash']), intval($this->data['uid']) ); @@ -208,13 +208,13 @@ class File extends DAV\Node implements DAV\IFile { return; } - $limit = service_class_fetch($c[0]['channel_id'], 'attach_upload_limit'); + $limit = engr_units_to_bytes(service_class_fetch($c[0]['channel_id'], 'attach_upload_limit')); if ($limit !== false) { $x = q("select sum(filesize) as total from attach where aid = %d ", intval($c[0]['channel_account_id']) ); if (($x) && ($x[0]['total'] + $size > $limit)) { - logger('service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); + logger('service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . userReadableSize($limit)); attach_delete($c[0]['channel_id'], $this->data['hash']); return; } @@ -236,7 +236,7 @@ class File extends DAV\Node implements DAV\IFile { logger('get file ' . basename($this->name), LOGGER_DEBUG); logger('os_path: ' . $this->os_path, LOGGER_DATA); - $r = q("SELECT data, flags, os_storage, filename, filetype FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", + $r = q("SELECT content, flags, os_storage, filename, filetype FROM attach WHERE hash = '%s' AND uid = %d LIMIT 1", dbesc($this->data['hash']), intval($this->data['uid']) ); @@ -250,14 +250,14 @@ class File extends DAV\Node implements DAV\IFile { } if (intval($r[0]['os_storage'])) { - $x = dbunescbin($r[0]['data']); + $x = dbunescbin($r[0]['content']); if(strpos($x,'store') === false) $f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $x; else $f = $x; return fopen($f, 'rb'); } - return dbunescbin($r[0]['data']); + return dbunescbin($r[0]['content']); } } @@ -337,6 +337,10 @@ class File extends DAV\Node implements DAV\IFile { } } + if(get_pconfig($this->auth->owner_id,'system','os_delete_prohibit') && \App::$module == 'dav') { + throw new DAV\Exception\Forbidden('Permission denied.'); + } + attach_delete($this->auth->owner_id, $this->data['hash']); $ch = channelx_by_n($this->auth->owner_id); diff --git a/Zotlabs/Storage/GitRepo.php b/Zotlabs/Storage/GitRepo.php new file mode 100644 index 000000000..306abc0ba --- /dev/null +++ b/Zotlabs/Storage/GitRepo.php @@ -0,0 +1,159 @@ + + */ +class GitRepo { + + public $url = null; + public $name = null; + private $path = null; + private $channel = null; + public $git = null; + private $repoBasePath = null; + + function __construct($channel = 'sys', $url = null, $clone = false, $name = null, $path = null) { + + if ($channel === 'sys' && !is_site_admin()) { + logger('Only admin can use channel sys'); + return null; + } + + $this->repoBasePath = __DIR__ . '/../../store/git'; + $this->channel = $channel; + $this->git = new PHPGit(); + + // Allow custom path for repo in the case of , for example + if ($path) { + $this->path = $path; + } else { + $this->path = $this->repoBasePath . "/" . $this->channel . "/" . $this->name; + } + + if ($this->isValidGitRepoURL($url)) { + $this->url = $url; + } + + if ($name) { + $this->name = $name; + } else { + $this->name = $this->getRepoNameFromURL($url); + } + if (!$this->name) { + logger('Error creating GitRepo. No repo name found.'); + return null; + } + + if (is_dir($this->path)) { + // ignore the $url input if it exists + // TODO: Check if the path is either empty or is a valid git repo and error if not + $this->git->setRepository($this->path); + // TODO: get repo metadata + return; + } + + if ($this->url) { + // create the folder and clone the repo at url to that folder if $clone is true + if ($clone) { + if (mkdir($this->path, 0770, true)) { + $this->git->setRepository($this->path); + if (!$this->cloneRepo()) { + // TODO: throw error + logger('git clone failed: ' . json_encode($this->git)); + } + } else { + logger('git repo path could not be created: ' . json_encode($this->git)); + } + } + } + } + + public function initRepo() { + if(!$this->path) return false; + try { + return $this->git->init($this->path); + } catch (\PHPGit\Exception\GitException $ex) { + return false; + } + } + + public function pull() { + try { + $success = $this->git->pull(); + } catch (\PHPGit\Exception\GitException $ex) { + return false; + } + return $success; + } + + public function getRepoPath() { + return $this->path; + } + + public function setRepoPath($directory) { + if (is_dir($directory)) { + $this->path->$directory; + $this->git->setRepository($directory); + return true; + } + return false; + } + + public function setIdentity($user_name, $user_email) { + // setup user for commit messages + $this->git->config->set("user.name", $user_name, ['global' => false, 'system' => false]); + $this->git->config->set("user.email", $user_email, ['global' => false, 'system' => false]); + } + + public function cloneRepo() { + if (validate_url($this->url) && $this->isValidGitRepoURL($this->url) && is_dir($this->path)) { + return $this->git->clone($this->url, $this->path); + } + } + + public function probeRepo() { + $git = $this->git; + $repo = array(); + $repo['remote'] = $git->remote(); + $repo['branches'] = $git->branch(['all' => true]); + $repo['logs'] = $git->log(array('limit' => 50)); + return $repo; + } + + // Commit changes to the repo. Default is to stage all changes and commit everything. + public function commit($msg, $options = array()) { + try { + return $this->git->commit($msg, $options); + } catch (\PHPGit\Exception\GitException $ex) { + return false; + } + } + + public static function isValidGitRepoURL($url) { + if (validate_url($url) && strrpos(parse_url($url, PHP_URL_PATH), '.')) { + return true; + } else { + return false; + } + } + + public static function getRepoNameFromURL($url) { + $urlpath = parse_url($url, PHP_URL_PATH); + $lastslash = strrpos($urlpath, '/') + 1; + $gitext = strrpos($urlpath, '.'); + if ($gitext) { + return substr($urlpath, $lastslash, $gitext - $lastslash); + } else { + return null; + } + } + +} diff --git a/Zotlabs/Text/Tagadelic.php b/Zotlabs/Text/Tagadelic.php new file mode 100644 index 000000000..55ecf2d75 --- /dev/null +++ b/Zotlabs/Text/Tagadelic.php @@ -0,0 +1,44 @@ +jsdisabled = 1; + else + $this->jsdisabled = 0; if(intval($_COOKIE['jsdisabled'])) $this->jsdisabled = 1; + else + $this->jsdisabled = 0; if(! $this->jsdisabled) { $page = urlencode(\App::$query_string); if($test) { - \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + self::$jsdisabled = 1; + if(array_key_exists('jsdisabled',$_COOKIE)) + self::$jsdisabled = $_COOKIE['jsdisabled']; + + if(! array_key_exists('jsdisabled',$_COOKIE)) { + \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; + /* emulate JS cookie if cookies are not accepted */ + if (array_key_exists('jsdisabled',$_GET)) { + $_COOKIE['jsdisabled'] = $_GET['jsdisabled']; + } + } } else { \App::$page['htmlhead'] .= "\r\n" . '' . "\r\n"; diff --git a/Zotlabs/Web/Controller.php b/Zotlabs/Web/Controller.php new file mode 100644 index 000000000..2d0f58891 --- /dev/null +++ b/Zotlabs/Web/Controller.php @@ -0,0 +1,13 @@ +controller = new $modname; \App::$module_loaded = true; + } + elseif(function_exists($module . '_module')) { + \App::$module_loaded = true; + } } if((strpos($module,'admin') === 0) && (! is_site_admin())) { @@ -50,33 +68,54 @@ class Router { /** * If the site has a custom module to over-ride the standard module, use it. - * Otherwise, look for the standard program module in the 'mod' directory + * Otherwise, look for the standard program module */ if(! (\App::$module_loaded)) { - if(file_exists("mod/site/{$module}.php")) { - include_once("mod/site/{$module}.php"); - \App::$module_loaded = true; + try { + $filename = 'Zotlabs/SiteModule/'. ucfirst($module). '.php'; + if(file_exists($filename)) { + // This won't be picked up by the autoloader, so load it explicitly + require_once($filename); + $this->controller = new $modname; + \App::$module_loaded = true; + } + else { + $filename = 'Zotlabs/Module/'. ucfirst($module). '.php'; + if(file_exists($filename)) { + $this->controller = new $modname; + \App::$module_loaded = true; + } + } + if(! \App::$module_loaded) + throw new \Exception('Module not found'); } - elseif(file_exists("mod/{$module}.php")) { - include_once("mod/{$module}.php"); - \App::$module_loaded = true; + catch(\Exception $e) { + if(file_exists("mod/site/{$module}.php")) { + include_once("mod/site/{$module}.php"); + \App::$module_loaded = true; + } + elseif(file_exists("mod/{$module}.php")) { + include_once("mod/{$module}.php"); + \App::$module_loaded = true; + } } - else logger("mod/{$module}.php not found."); } - - + /** - * This provides a place for plugins to register module handlers which don't otherwise exist on the system. + * This provides a place for plugins to register module handlers which don't otherwise exist + * on the system, or to completely over-ride an existing module. * If the plugin sets 'installed' to true we won't throw a 404 error for the specified module even if * there is no specific module file or matching plugin name. * The plugin should catch at least one of the module hooks for this URL. */ - $x = array('module' => $module, 'installed' => false); + $x = array('module' => $module, 'installed' => \App::$module_loaded, 'controller' => $this->controller); call_hooks('module_loaded', $x); - if($x['installed']) + if($x['installed']) { \App::$module_loaded = true; + $this->controller = $x['controller']; + } /** * The URL provided does not resolve to a valid module. @@ -96,6 +135,8 @@ class Router { killme(); } + logger("Module {$module} not found.", LOGGER_DEBUG, LOG_WARNING); + if((x($_SERVER, 'QUERY_STRING')) && ($_SERVER['QUERY_STRING'] === 'q=internal_error.html') && \App::$config['system']['dreamhost_error_hack']) { logger('index.php: dreamhost_error_hack invoked. Original URI =' . $_SERVER['REQUEST_URI']); goaway(z_root() . $_SERVER['REQUEST_URI']); @@ -133,17 +174,20 @@ class Router { * to over-ride them. */ - if(function_exists(\App::$module . '_init')) { - $arr = array('init' => true, 'replace' => false); - call_hooks(\App::$module . '_mod_init', $arr); - if(! $arr['replace']) { + $arr = array('init' => true, 'replace' => false); + call_hooks(\App::$module . '_mod_init', $arr); + if(! $arr['replace']) { + if($this->controller && method_exists($this->controller,'init')) { + $this->controller->init(); + } + elseif(function_exists(\App::$module . '_init')) { $func = \App::$module . '_init'; $func($a); } } /** - * Do all theme initialiasion here before calling any additional module functions. + * Do all theme initialisation here before calling any additional module functions. * The module_init function may have changed the theme. * Additionally any page with a Comanche template may alter the theme. * So we'll check for those now. @@ -162,13 +206,15 @@ class Router { * load current theme info */ - $theme_info_file = 'view/theme/' . current_theme() . '/php/theme.php'; + $current_theme = \Zotlabs\Render\Theme::current(); + + $theme_info_file = 'view/theme/' . $current_theme[0] . '/php/theme.php'; if (file_exists($theme_info_file)){ require_once($theme_info_file); } - if(function_exists(str_replace('-', '_', current_theme()) . '_init')) { - $func = str_replace('-', '_', current_theme()) . '_init'; + if(function_exists(str_replace('-', '_', $current_theme[0]) . '_init')) { + $func = str_replace('-', '_', $current_theme[0]) . '_init'; $func($a); } elseif (x(\App::$theme_info, 'extends') && file_exists('view/theme/' . \App::$theme_info['extends'] . '/php/theme.php')) { @@ -179,21 +225,30 @@ class Router { } } - if(($_SERVER['REQUEST_METHOD'] === 'POST') && (! \App::$error) - && (function_exists(\App::$module . '_post')) - && (! x($_POST, 'auth-params'))) { + if(($_SERVER['REQUEST_METHOD'] === 'POST') && (! \App::$error) && (! x($_POST, 'auth-params'))) { call_hooks(\App::$module . '_mod_post', $_POST); - $func = \App::$module . '_post'; - $func($a); + + if($this->controller && method_exists($this->controller,'post')) { + $this->controller->post(); + } + elseif(function_exists(\App::$module . '_post')) { + $func = \App::$module . '_post'; + $func($a); + } } - if((! \App::$error) && (function_exists(\App::$module . '_content'))) { + if(! \App::$error) { $arr = array('content' => \App::$page['content'], 'replace' => false); call_hooks(\App::$module . '_mod_content', $arr); \App::$page['content'] = $arr['content']; if(! $arr['replace']) { - $func = \App::$module . '_content'; - $arr = array('content' => $func($a)); + if($this->controller && method_exists($this->controller,'get')) { + $arr = array('content' => $this->controller->get()); + } + elseif(function_exists(\App::$module . '_content')) { + $func = \App::$module . '_content'; + $arr = array('content' => $func($a)); + } } call_hooks(\App::$module . '_mod_aftercontent', $arr); \App::$page['content'] .= $arr['content']; diff --git a/Zotlabs/Web/Session.php b/Zotlabs/Web/Session.php index f998df396..4f2a3f1f7 100644 --- a/Zotlabs/Web/Session.php +++ b/Zotlabs/Web/Session.php @@ -13,10 +13,10 @@ namespace Zotlabs\Web; class Session { - private static $handler = null; - private static $session_started = false; + private $handler = null; + private $session_started = false; - function init() { + public function init() { $gc_probability = 50; @@ -29,28 +29,38 @@ class Session { */ $handler = new \Zotlabs\Web\SessionHandler(); - self::$handler = $handler; - $x = session_set_save_handler($handler,true); + $this->handler = $handler; + + $x = session_set_save_handler($handler,false); if(! $x) logger('Session save handler initialisation failed.',LOGGER_NORMAL,LOG_ERR); // Force cookies to be secure (https only) if this site is SSL enabled. // Must be done before session_start(). + $arr = session_get_cookie_params(); + + // Note when setting cookies: set the domain to false which creates a single domain + // cookie. If you use a hostname it will create a .domain.com wildcard which will + // have some nasty side effects if you have any other subdomains running hubzilla. + session_set_cookie_params( ((isset($arr['lifetime'])) ? $arr['lifetime'] : 0), ((isset($arr['path'])) ? $arr['path'] : '/'), - ((isset($arr['domain'])) ? $arr['domain'] : App::get_hostname()), + (($arr['domain']) ? $arr['domain'] : false), ((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ? true : false), ((isset($arr['httponly'])) ? $arr['httponly'] : true) ); + + register_shutdown_function('session_write_close'); + } - function start() { + public function start() { session_start(); - self::$session_started = true; + $this->session_started = true; } /** @@ -59,8 +69,8 @@ class Session { * @return void */ - function nuke() { - self::new_cookie(0); // 0 means delete on browser exit + public function nuke() { + $this->new_cookie(0); // 0 means delete on browser exit if($_SESSION && count($_SESSION)) { foreach($_SESSION as $k => $v) { unset($_SESSION[$k]); @@ -68,48 +78,53 @@ class Session { } } - function new_cookie($xtime) { + public function new_cookie($xtime) { $newxtime = (($xtime> 0) ? (time() + $xtime) : 0); $old_sid = session_id(); - if(self::$handler && self::$session_started) { + $arr = session_get_cookie_params(); + + if($this->handler && $this->session_started) { + session_regenerate_id(true); // force SessionHandler record creation with the new session_id // which occurs as a side effect of read() - self::$handler->read(session_id()); + $this->handler->read(session_id()); } else logger('no session handler'); if (x($_COOKIE, 'jsdisabled')) { - setcookie('jsdisabled', $_COOKIE['jsdisabled'], $newxtime); + setcookie('jsdisabled', $_COOKIE['jsdisabled'], $newxtime, '/', false,((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ? true : false),((isset($arr['httponly'])) ? $arr['httponly'] : true)); } - setcookie(session_name(),session_id(),$newxtime); + setcookie(session_name(),session_id(),$newxtime, '/', false,((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ? true : false),((isset($arr['httponly'])) ? $arr['httponly'] : true)); $arr = array('expire' => $xtime); call_hooks('new_cookie', $arr); } - function extend_cookie() { + public function extend_cookie() { + + $arr = session_get_cookie_params(); // if there's a long-term cookie, extend it $xtime = (($_SESSION['remember_me']) ? (60 * 60 * 24 * 365) : 0 ); if($xtime) - setcookie(session_name(),session_id(),(time() + $xtime)); + setcookie(session_name(),session_id(),(time() + $xtime), '/', false,((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') ? true : false),((isset($arr['httponly'])) ? $arr['httponly'] : true)); $arr = array('expire' => $xtime); call_hooks('extend_cookie', $arr); } - function return_check() { + public function return_check() { // check a returning visitor against IP changes. // If the change results in being blocked from re-entry with the current cookie @@ -149,7 +164,7 @@ class Session { // check any difference at all logger('Session address changed. Paranoid setting in effect, blocking session. ' . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']); - self::nuke(); + $this->nuke(); goaway(z_root()); break; } diff --git a/Zotlabs/Web/SessionHandler.php b/Zotlabs/Web/SessionHandler.php index 6980a6408..93b27a7e8 100644 --- a/Zotlabs/Web/SessionHandler.php +++ b/Zotlabs/Web/SessionHandler.php @@ -18,13 +18,14 @@ class SessionHandler implements \SessionHandlerInterface { function read ($id) { if($id) { - $r = q("SELECT `data` FROM `session` WHERE `sid`= '%s'", dbesc($id)); + $r = q("SELECT `sess_data` FROM `session` WHERE `sid`= '%s'", dbesc($id)); if($r) { - return $r[0]['data']; + return $r[0]['sess_data']; } else { - q("INSERT INTO `session` (sid, expire) values ('%s', '%s')", + q("INSERT INTO `session` (sess_data, sid, expire) values ('%s', '%s', '%s')", + dbesc(''), dbesc($id), dbesc(time() + 300) ); @@ -59,7 +60,7 @@ class SessionHandler implements \SessionHandlerInterface { } q("UPDATE `session` - SET `data` = '%s', `expire` = '%s' WHERE `sid` = '%s'", + SET `sess_data` = '%s', `expire` = '%s' WHERE `sid` = '%s'", dbesc($data), dbesc($expire), dbesc($id) diff --git a/Zotlabs/Web/WebServer.php b/Zotlabs/Web/WebServer.php new file mode 100644 index 000000000..d4f3cb9ea --- /dev/null +++ b/Zotlabs/Web/WebServer.php @@ -0,0 +1,137 @@ +start(); + } + else { + session_start(); + register_shutdown_function('session_write_close'); + } + + /** + * Language was set earlier, but we can over-ride it in the session. + * We have to do it here because the session was just now opened. + */ + + if(array_key_exists('system_language',$_POST)) { + if(strlen($_POST['system_language'])) + $_SESSION['language'] = $_POST['system_language']; + else + unset($_SESSION['language']); + } + if((x($_SESSION, 'language')) && ($_SESSION['language'] !== $lang)) { + \App::$language = $_SESSION['language']; + load_translation_table(\App::$language); + } + + if((x($_GET,'zid')) && (! \App::$install)) { + \App::$query_string = strip_zids(\App::$query_string); + if(! local_channel()) { + $_SESSION['my_address'] = $_GET['zid']; + zid_init(); + } + } + + if((x($_GET,'zat')) && (! \App::$install)) { + \App::$query_string = strip_zats(\App::$query_string); + if(! local_channel()) { + zat_init(); + } + } + + if((x($_SESSION, 'authenticated')) || (x($_POST, 'auth-params')) || (\App::$module === 'login')) + require('include/auth.php'); + + if(! x($_SESSION, 'sysmsg')) + $_SESSION['sysmsg'] = array(); + + if(! x($_SESSION, 'sysmsg_info')) + $_SESSION['sysmsg_info'] = array(); + + /* + * check_config() is responsible for running update scripts. These automatically + * update the DB schema whenever we push a new one out. It also checks to see if + * any plugins have been added or removed and reacts accordingly. + */ + + + if(\App::$install) { + /* Allow an exception for the view module so that pcss will be interpreted during installation */ + if(\App::$module != 'view') + \App::$module = 'setup'; + } + else + check_config($a); + + nav_set_selected('nothing'); + + $Router = new Router($a); + + /* initialise content region */ + + if(! x(\App::$page, 'content')) + \App::$page['content'] = ''; + + call_hooks('page_content_top', \App::$page['content']); + + + $Router->Dispatch($a); + + + // If you're just visiting, let javascript take you home + + if(x($_SESSION, 'visitor_home')) { + $homebase = $_SESSION['visitor_home']; + } elseif(local_channel()) { + $homebase = z_root() . '/channel/' . \App::$channel['channel_address']; + } + + if(isset($homebase)) { + \App::$page['content'] .= ''; + } + + // now that we've been through the module content, see if the page reported + // a permission problem and if so, a 403 response would seem to be in order. + + if(stristr(implode("", $_SESSION['sysmsg']), t('Permission denied'))) { + header($_SERVER['SERVER_PROTOCOL'] . ' 403 ' . t('Permission denied.')); + } + + call_hooks('page_end', \App::$page['content']); + + construct_page($a); + + killme(); + } +} \ No newline at end of file diff --git a/Zotlabs/Zot/Auth.php b/Zotlabs/Zot/Auth.php index f764172fa..0837be21a 100644 --- a/Zotlabs/Zot/Auth.php +++ b/Zotlabs/Zot/Auth.php @@ -80,11 +80,9 @@ class Auth { if(! $x) { // finger them if they can't be found. - $ret = zot_finger($address, null); - if ($ret['success']) { - $j = json_decode($ret['body'], true); - if($j) - import_xchan($j); + $j = Finger::run($address, null); + if ($j['success']) { + import_xchan($j); $x = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_addr = '%s' order by hubloc_id desc", dbesc($address) diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php new file mode 100644 index 000000000..e7603442f --- /dev/null +++ b/Zotlabs/Zot/Finger.php @@ -0,0 +1,130 @@ + true) or array('success' => false); + */ + + static public function run($webbie, $channel = null, $autofallback = true) { + + $ret = array('success' => false); + + self::$token = random_string(); + + if (strpos($webbie,'@') === false) { + $address = $webbie; + $host = \App::get_hostname(); + } else { + $address = substr($webbie,0,strpos($webbie,'@')); + $host = substr($webbie,strpos($webbie,'@')+1); + } + + $xchan_addr = $address . '@' . $host; + + if ((! $address) || (! $xchan_addr)) { + logger('zot_finger: no address :' . $webbie); + return $ret; + } + + logger('using xchan_addr: ' . $xchan_addr, LOGGER_DATA, LOG_DEBUG); + + // potential issue here; the xchan_addr points to the primary hub. + // The webbie we were called with may not, so it might not be found + // unless we query for hubloc_addr instead of xchan_addr + + $r = q("select xchan.*, hubloc.* from xchan + left join hubloc on xchan_hash = hubloc_hash + where xchan_addr = '%s' and hubloc_primary = 1 limit 1", + dbesc($xchan_addr) + ); + + if ($r) { + $url = $r[0]['hubloc_url']; + + if ($r[0]['hubloc_network'] && $r[0]['hubloc_network'] !== 'zot') { + logger('zot_finger: alternate network: ' . $webbie); + logger('url: '.$url.', net: '.var_export($r[0]['hubloc_network'],true), LOGGER_DATA, LOG_DEBUG); + return $ret; + } + } + else { + $url = 'https://' . $host; + } + + $rhs = '/.well-known/zot-info'; + $https = ((strpos($url,'https://') === 0) ? true : false); + + logger('zot_finger: ' . $address . ' at ' . $url, LOGGER_DEBUG); + + if ($channel) { + $postvars = array( + 'address' => $address, + 'target' => $channel['channel_guid'], + 'target_sig' => $channel['channel_guid_sig'], + 'key' => $channel['channel_pubkey'], + 'token' => self::$token + ); + + $result = z_post_url($url . $rhs,$postvars); + + if ((! $result['success']) && ($autofallback)) { + if ($https) { + logger('zot_finger: https failed. falling back to http'); + $result = z_post_url('http://' . $host . $rhs,$postvars); + } + } + } + else { + $rhs .= '?f=&address=' . urlencode($address) . '&token=' . self::$token; + + $result = z_fetch_url($url . $rhs); + if ((! $result['success']) && ($autofallback)) { + if ($https) { + logger('zot_finger: https failed. falling back to http'); + $result = z_fetch_url('http://' . $host . $rhs); + } + } + } + + if(! $result['success']) { + logger('zot_finger: no results'); + return $ret; + } + + $x = json_decode($result['body'],true); + if($x) { + $signed_token = ((is_array($x) && array_key_exists('signed_token',$x)) ? $x['signed_token'] : null); + if($signed_token) { + $valid = rsa_verify('token.' . self::$token,base64url_decode($signed_token),$x['key']); + if(! $valid) { + logger('invalid signed token: ' . $url . $rhs, LOGGER_NORMAL, LOG_ERR); + return $ret; + } + } + else { + logger('No signed token from ' . $url . $rhs, LOGGER_NORMAL, LOG_WARNING); + // after 2017-01-01 this will be a hard error unless you over-ride it. + if((time() > 1483228800) && (! get_config('system','allow_unsigned_zotfinger'))) + return $ret; + } + } + + return $x; + } + +} diff --git a/Zotlabs/Zot/Verify.php b/Zotlabs/Zot/Verify.php index 1192202db..06bd3188c 100644 --- a/Zotlabs/Zot/Verify.php +++ b/Zotlabs/Zot/Verify.php @@ -6,7 +6,7 @@ namespace Zotlabs\Zot; class Verify { function create($type,$channel_id,$token,$meta) { - return q("insert into verify ( type, channel, token, meta, created ) values ( '%s', %d, '%s', '%s', '%s' )", + return q("insert into verify ( vtype, channel, token, meta, created ) values ( '%s', %d, '%s', '%s', '%s' )", dbesc($type), intval($channel_id), dbesc($token), @@ -16,7 +16,7 @@ class Verify { } function match($type,$channel_id,$token,$meta) { - $r = q("select id from verify where type = '%s' and channel = %d and token = '%s' and meta = '%s' limit 1", + $r = q("select id from verify where vtype = '%s' and channel = %d and token = '%s' and meta = '%s' limit 1", dbesc($type), intval($channel_id), dbesc($token), @@ -32,7 +32,7 @@ class Verify { } function purge($type,$interval) { - q("delete from verify where type = '%s' and created < %s - INTERVAL %s", + q("delete from verify where vtype = '%s' and created < %s - INTERVAL %s", dbesc($type), db_utcnow(), db_quoteinterval($interval) diff --git a/app/bookmarks.apd b/app/bookmarks.apd index 9581a220c..2a04d9bf3 100644 --- a/app/bookmarks.apd +++ b/app/bookmarks.apd @@ -1,4 +1,4 @@ url: $baseurl/bookmarks requires: local_channel -name: View bookmarks +name: View Bookmarks photo: $baseurl/app/bookmarks.png diff --git a/app/chat.apd b/app/chat.apd index d4879c0b7..7ba1cd2c8 100644 --- a/app/chat.apd +++ b/app/chat.apd @@ -1,4 +1,4 @@ url: $baseurl/chat/$nick requires: local_channel -name: My chatrooms +name: My Chatrooms photo: $baseurl/app/chat.png diff --git a/app/probe.apd b/app/probe.apd index e1ab5fc43..910e628ba 100644 --- a/app/probe.apd +++ b/app/probe.apd @@ -1,4 +1,4 @@ url: $baseurl/probe requires: local_channel -name: Remote diagnostics +name: Remote Diagnostics photo: $baseurl/app/probe.png diff --git a/app/profile.apd b/app/profile.apd index d353d5742..48e5d5814 100644 --- a/app/profile.apd +++ b/app/profile.apd @@ -1,4 +1,4 @@ url: $baseurl/profile/$nick requires: local_channel -name: View profile +name: View Profile photo: $baseurl/app/profile.png diff --git a/app/suggest.apd b/app/suggest.apd index f3d17e0ea..cd94a6d1f 100644 --- a/app/suggest.apd +++ b/app/suggest.apd @@ -1,4 +1,4 @@ url: $baseurl/suggest requires: local_channel -name: Suggest channels +name: Suggest Channels photo: $baseurl/app/suggest.png diff --git a/app/wiki.apd b/app/wiki.apd new file mode 100644 index 000000000..95245055c --- /dev/null +++ b/app/wiki.apd @@ -0,0 +1,4 @@ +url: $baseurl/wiki/$nick +requires: local_channel +name: Wiki +photo: $baseurl/app/wiki.png diff --git a/app/wiki.png b/app/wiki.png new file mode 100644 index 000000000..31d981679 Binary files /dev/null and b/app/wiki.png differ diff --git a/boot.php b/boot.php index ef620e3ec..de483035e 100755 --- a/boot.php +++ b/boot.php @@ -34,23 +34,20 @@ require_once('include/text.php'); require_once('include/datetime.php'); require_once('include/language.php'); require_once('include/nav.php'); -require_once('include/cache.php'); require_once('include/permissions.php'); require_once('library/Mobile_Detect/Mobile_Detect.php'); -require_once('include/BaseObject.php'); require_once('include/features.php'); require_once('include/taxonomy.php'); -require_once('include/identity.php'); -require_once('include/Contact.php'); +require_once('include/channel.php'); +require_once('include/connections.php'); require_once('include/account.php'); define ( 'PLATFORM_NAME', 'hubzilla' ); -define ( 'RED_VERSION', trim(file_get_contents('version.inc'))); -define ( 'STD_VERSION', '1.4' ); -define ( 'ZOT_REVISION', 1 ); +define ( 'STD_VERSION', '1.11' ); +define ( 'ZOT_REVISION', '1.1' ); -define ( 'DB_UPDATE_VERSION', 1165 ); +define ( 'DB_UPDATE_VERSION', 1181 ); /** @@ -314,15 +311,14 @@ define ( 'PERMS_A_REPUBLISH', 0x10000); define ( 'PERMS_W_LIKE', 0x20000); // General channel permissions - -define ( 'PERMS_PUBLIC' , 0x0001 ); -define ( 'PERMS_NETWORK' , 0x0002 ); -define ( 'PERMS_SITE' , 0x0004 ); -define ( 'PERMS_CONTACTS' , 0x0008 ); -define ( 'PERMS_SPECIFIC' , 0x0080 ); -define ( 'PERMS_AUTHED' , 0x0100 ); -define ( 'PERMS_PENDING' , 0x0200 ); - + // 0 = Only you +define ( 'PERMS_PUBLIC' , 0x0001 ); // anybody +define ( 'PERMS_NETWORK' , 0x0002 ); // anybody in this network +define ( 'PERMS_SITE' , 0x0004 ); // anybody on this site +define ( 'PERMS_CONTACTS' , 0x0008 ); // any of my connections +define ( 'PERMS_SPECIFIC' , 0x0080 ); // only specific connections +define ( 'PERMS_AUTHED' , 0x0100 ); // anybody authenticated (could include visitors from other networks) +define ( 'PERMS_PENDING' , 0x0200 ); // any connections including those who haven't yet been approved // Address book flags @@ -476,6 +472,7 @@ define ( 'NAMESPACE_YMEDIA', 'http://search.yahoo.com/mrss/' ); * activity stream defines */ +define ( 'ACTIVITY_REACT', NAMESPACE_ZOT . '/activity/react' ); define ( 'ACTIVITY_LIKE', NAMESPACE_ACTIVITY_SCHEMA . 'like' ); define ( 'ACTIVITY_DISLIKE', NAMESPACE_ZOT . '/activity/dislike' ); define ( 'ACTIVITY_AGREE', NAMESPACE_ZOT . '/activity/agree' ); @@ -516,6 +513,7 @@ define ( 'ACTIVITY_OBJ_ALBUM', NAMESPACE_ACTIVITY_SCHEMA . 'photo-album' ); define ( 'ACTIVITY_OBJ_EVENT', NAMESPACE_ACTIVITY_SCHEMA . 'event' ); define ( 'ACTIVITY_OBJ_GROUP', NAMESPACE_ACTIVITY_SCHEMA . 'group' ); define ( 'ACTIVITY_OBJ_GAME', NAMESPACE_ACTIVITY_SCHEMA . 'game' ); +define ( 'ACTIVITY_OBJ_WIKI', NAMESPACE_ACTIVITY_SCHEMA . 'wiki' ); define ( 'ACTIVITY_OBJ_TAGTERM', NAMESPACE_ZOT . '/activity/tagterm' ); define ( 'ACTIVITY_OBJ_PROFILE', NAMESPACE_ZOT . '/activity/profile' ); define ( 'ACTIVITY_OBJ_THING', NAMESPACE_ZOT . '/activity/thing' ); @@ -582,6 +580,72 @@ define ( 'ITEM_IS_STICKY', 1000 ); define ( 'DBTYPE_MYSQL', 0 ); define ( 'DBTYPE_POSTGRES', 1 ); + +function sys_boot() { + + // our central App object + + App::init(); + + /* + * Load the configuration file which contains our DB credentials. + * Ignore errors. If the file doesn't exist or is empty, we are running in + * installation mode. + */ + + // miniApp is a conversion object from old style .htconfig.php files + + $a = new miniApp; + + + App::$install = ((file_exists('.htconfig.php') && filesize('.htconfig.php')) ? false : true); + + @include('.htconfig.php'); + + if(! defined('UNO')) + define('UNO', 0); + + if(array_key_exists('default_timezone',get_defined_vars())) { + App::$config['system']['timezone'] = $default_timezone; + } + + $a->convert(); + + App::$timezone = ((App::$config['system']['timezone']) ? App::$config['system']['timezone'] : 'UTC'); + date_default_timezone_set(App::$timezone); + + + /* + * Try to open the database; + */ + + require_once('include/dba/dba_driver.php'); + + if(! App::$install) { + DBA::dba_factory($db_host, $db_port, $db_user, $db_pass, $db_data, $db_type, App::$install); + if(! DBA::$dba->connected) { + system_unavailable(); + } + + unset($db_host, $db_port, $db_user, $db_pass, $db_data, $db_type); + + /** + * Load configs from db. Overwrite configs from .htconfig.php + */ + + load_config('config'); + load_config('system'); + load_config('feature'); + + App::$session = new Zotlabs\Web\Session(); + App::$session->init(); + load_hooks(); + call_hooks('init_1'); + } + +} + + /** * * Reverse the effect of magic_quotes_gpc if it is enabled. @@ -628,12 +692,25 @@ function startup() { class ZotlabsAutoloader { static public function loader($className) { $filename = str_replace('\\', '/', $className) . ".php"; - if (file_exists($filename)) { + if(file_exists($filename)) { include($filename); if (class_exists($className)) { return TRUE; } } + $arr = explode('\\',$className); + if($arr && count($arr) > 1) { + if(! $arr[0]) + $arr = array_shift($arr); + $filename = 'addon/' . lcfirst($arr[0]) . '/' . $arr[1] . ((count($arr) === 2) ? '.php' : '/' . $arr[2] . ".php"); + if(file_exists($filename)) { + include($filename); + if (class_exists($className)) { + return TRUE; + } + } + } + return FALSE; } } @@ -689,9 +766,11 @@ class App { private static $perms = null; // observer permissions private static $widgets = array(); // widgets for this page + public static $session = null; public static $groups; public static $language; public static $langsave; + public static $rtl = false; public static $plugins_admin; public static $module_loaded = false; public static $query_string; @@ -705,6 +784,7 @@ class App { public static $content; public static $data = array(); public static $error = false; + public static $emojitab = false; public static $cmd; public static $argv; public static $argc; @@ -725,6 +805,7 @@ class App { public static $nav_sel; public static $is_mobile = false; public static $is_tablet = false; + public static $comanche; public static $category; @@ -787,7 +868,7 @@ class App { /** * App constructor. */ - function init() { + public static function init() { // we'll reset this after we read our config file date_default_timezone_set('UTC'); @@ -860,6 +941,8 @@ class App { if ((array_key_exists('0', self::$argv)) && strlen(self::$argv[0])) { self::$module = str_replace(".", "_", self::$argv[0]); self::$module = str_replace("-", "_", self::$module); + if(strpos(self::$module,'_') === 0) + self::$module = substr(self::$module,1); } else { self::$argc = 1; self::$argv = array('home'); @@ -888,21 +971,28 @@ class App { self::head_set_icon('/images/hz-32.png'); - BaseObject::set_app($this); - /* * register template engines */ - $dc = get_declared_classes(); - foreach ($dc as $k) { - if (in_array("ITemplateEngine", class_implements($k))){ - self::register_template_engine($k); - } - } spl_autoload_register('ZotlabsAutoloader::loader'); self::$meta= new Zotlabs\Web\HttpMeta(); + + // create an instance of the smarty template engine so we can register it. + + $smarty = new Zotlabs\Render\SmartyTemplate(); + + $dc = get_declared_classes(); + + foreach ($dc as $k) { + if(in_array('Zotlabs\\Render\\TemplateEngine', class_implements($k))) { + self::register_template_engine($k); + } + } + + + } public static function get_baseurl($ssl = false) { @@ -1055,7 +1145,7 @@ class App { if(! self::$meta->get_field('og:title')) self::$meta->set('og:title',self::$page['title']); - self::$meta->set('generator', Zotlabs\Project\System::get_platform_name()); + self::$meta->set('generator', Zotlabs\Lib\System::get_platform_name()); /* put the head template at the beginning of page['htmlhead'] * since the code added by the modules frequently depends on it @@ -1070,7 +1160,7 @@ class App { '$local_channel' => local_channel(), '$metas' => self::$meta->get(), '$update_interval' => $interval, - 'osearch' => sprintf( t('Search %1$s (%2$s)','opensearch'), Zotlabs\Project\System::get_site_name(), t('$Projectname','opensearch')), + 'osearch' => sprintf( t('Search %1$s (%2$s)','opensearch'), Zotlabs\Lib\System::get_site_name(), t('$Projectname','opensearch')), '$icon' => head_get_icon(), '$head_css' => head_get_css(), '$head_js' => head_get_js(), @@ -1176,7 +1266,6 @@ class App { * @return App */ function get_app() { - global $a; return $a; } @@ -1226,7 +1315,6 @@ function system_unavailable() { function clean_urls() { - global $a; // if(App::$config['system']['clean_urls']) return true; @@ -1234,8 +1322,6 @@ function clean_urls() { } function z_path() { - global $a; - $base = z_root(); if(! clean_urls()) $base .= '/?q='; @@ -1251,7 +1337,6 @@ function z_path() { * @return string */ function z_root() { - global $a; return App::get_baseurl(); } @@ -1381,6 +1466,12 @@ function check_config(&$a) { @unlink($lockfile); //send the administrator an e-mail file_put_contents($lockfile, $x); + + $r = q("select account_language from account where account_email = '%s' limit 1", + dbesc(App::$config['system']['admin_email']) + ); + push_lang(($r) ? $r[0]['account_language'] : 'en'); + $email_tpl = get_intltext_template("update_fail_eml.tpl"); $email_msg = replace_macros($email_tpl, array( @@ -1398,6 +1489,7 @@ function check_config(&$a) { . 'Content-transfer-encoding: 8bit' ); //try the logger logger('CRITICAL: Update Failed: ' . $x); + pop_lang(); } else set_config('database','update_r' . $x, 'success'); @@ -1440,11 +1532,11 @@ function check_config(&$a) { if(count($installed)) { foreach($installed as $i) { - if(! in_array($i['name'], $plugins_arr)) { - unload_plugin($i['name']); + if(! in_array($i['aname'], $plugins_arr)) { + unload_plugin($i['aname']); } else { - $installed_arr[] = $i['name']; + $installed_arr[] = $i['aname']; } } } @@ -1541,7 +1633,20 @@ function fix_system_urls($oldurl, $newurl) { intval($c[0]['channel_id']) ); - proc_run('php', 'include/notifier.php', 'refresh_all', $c[0]['channel_id']); + $m = q("select abook_id, abook_instance from abook where abook_instance like '%s' and abook_channel = %d", + dbesc('%' . $oldurl . '%'), + intval($c[0]['channel_id']) + ); + if($m) { + foreach($m as $mm) { + q("update abook set abook_instance = '%s' where abook_id = %d", + dbesc(str_replace($oldurl,$newurl,$mm['abook_instance'])), + intval($mm['abook_id']) + ); + } + } + + Zotlabs\Daemon\Master::Summon(array('Notifier', 'refresh_all', $c[0]['channel_id'])); } } @@ -1570,7 +1675,6 @@ function fix_system_urls($oldurl, $newurl) { // returns the complete html for inserting into the page function login($register = false, $form_id = 'main-login', $hiddens=false) { - $a = get_app(); $o = ''; $reg = false; $reglink = get_config('system', 'register_link'); @@ -1599,7 +1703,7 @@ function login($register = false, $form_id = 'main-login', $hiddens=false) { '$logout' => t('Logout'), '$login' => t('Login'), '$form_id' => $form_id, - '$lname' => array('username', t('Email') , '', ''), + '$lname' => array('username', t('Login/Email') , '', ''), '$lpassword' => array('password', t('Password'), '', ''), '$remember_me' => array('remember_me', t('Remember me'), '', '',array(t('No'),t('Yes'))), '$hiddens' => $hiddens, @@ -1618,6 +1722,16 @@ function login($register = false, $form_id = 'main-login', $hiddens=false) { * @brief Used to end the current process, after saving session state. */ function killme() { + + // Ensure that closing the database is the last function on the shutdown stack. + // If it is closed prematurely sessions might not get saved correctly. + // Note the second arg to PHP's session_set_save_handler() seems to order that shutdown + // procedure last despite our best efforts, so we don't use that and implictly + // call register_shutdown_function('session_write_close'); within Zotlabs\Web\Session::init() + // and then register the database close function here where nothing else can register + // after it. + + register_shutdown_function('shutdown'); exit; } @@ -1629,6 +1743,10 @@ function goaway($s) { killme(); } +function shutdown() { + +} + /** * @brief Returns the entity id of locally logged in account or false. * @@ -1660,7 +1778,9 @@ function get_account_id() { * @return int|bool channel_id or false */ function local_channel() { - if((x($_SESSION, 'authenticated')) && (x($_SESSION, 'uid'))) + if(session_id() + && array_key_exists('authenticated',$_SESSION) && $_SESSION['authenticated'] + && array_key_exists('uid',$_SESSION) && intval($_SESSION['uid'])) return intval($_SESSION['uid']); return false; @@ -1691,7 +1811,9 @@ function local_user() { * @return string|bool visitor_id or false */ function remote_channel() { - if((x($_SESSION, 'authenticated')) && (x($_SESSION, 'visitor_id'))) + if(session_id() + && array_key_exists('authenticated',$_SESSION) && $_SESSION['authenticated'] + && array_key_exists('visitor_id',$_SESSION) && $_SESSION['visitor_id']) return $_SESSION['visitor_id']; return false; @@ -1716,7 +1838,9 @@ function remote_user() { * @param string $s Text to display */ function notice($s) { - $a = get_app(); + if(! session_id()) + return; + if(! x($_SESSION, 'sysmsg')) $_SESSION['sysmsg'] = array(); // ignore duplicated error messages which haven't yet been displayed @@ -1740,8 +1864,10 @@ function notice($s) { * @param string $s Text to display */ function info($s) { - $a = get_app(); - if(! x($_SESSION, 'sysmsg_info')) $_SESSION['sysmsg_info'] = array(); + if(! session_id()) + return; + if(! x($_SESSION, 'sysmsg_info')) + $_SESSION['sysmsg_info'] = array(); if(App::$interactive) $_SESSION['sysmsg_info'][] = $s; } @@ -1769,42 +1895,45 @@ function get_max_import_size() { * * $cmd and string args are surrounded with "" */ -function proc_run($cmd){ - - $a = get_app(); +function proc_run(){ $args = func_get_args(); $newargs = array(); + if(! count($args)) return; - // expand any arrays - - foreach($args as $arg) { - if(is_array($arg)) { - foreach($arg as $n) { - $newargs[] = $n; - } - } - else - $newargs[] = $arg; - } - - $args = $newargs; + $args = flatten_array_recursive($args); $arr = array('args' => $args, 'run_cmd' => true); - call_hooks("proc_run", $arr); + call_hooks('proc_run', $arr); + if(! $arr['run_cmd']) return; if(count($args) && $args[0] === 'php') $args[0] = ((x(App::$config,'system')) && (x(App::$config['system'],'php_path')) && (strlen(App::$config['system']['php_path'])) ? App::$config['system']['php_path'] : 'php'); - for($x = 0; $x < count($args); $x++) - $args[$x] = escapeshellarg($args[$x]); + // redirect proc_run statements of legacy daemon processes to the newer Daemon Master object class + // We will keep this interface until everybody has transitioned. (2016-05-20) + + if(strstr($args[1],'include/')) { + // convert 'include/foo.php' to 'Foo' + $orig = substr(ucfirst(substr($args[1],8)),0,-4); + logger('proc_run_redirect: ' . $orig); + if(file_exists('Zotlabs/Daemon/' . $orig . '.php')) { + array_shift($args); // daemons are all run by php, pop it off the top of the array + $args[0] = $orig; // replace with the new daemon name + logger('Redirecting old proc_run interface: ' . print_r($args,true), LOGGER_DEBUG, LOG_DEBUG); + \Zotlabs\Daemon\Master::Summon($args); // summon the daemon + return; + } + } + + $args = array_map('escapeshellarg',$args); $cmdline = implode($args," "); if(is_windows()) { @@ -1824,110 +1953,15 @@ function proc_run($cmd){ * @brief Checks if we are running on M$ Windows. * * @return bool true if we run on M$ Windows + * + * It's possible you might be able to run on WAMP or XAMPP, and this + * has been accomplished, but is not officially supported. Good luck. + * */ function is_windows() { return ((strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? true : false); } - -function current_theme(){ - $app_base_themes = array('redbasic'); - - $a = get_app(); - $page_theme = null; - - // Find the theme that belongs to the channel whose stuff we are looking at - - if(App::$profile_uid && App::$profile_uid != local_channel()) { - $r = q("select channel_theme from channel where channel_id = %d limit 1", - intval(App::$profile_uid) - ); - if($r) - $page_theme = $r[0]['channel_theme']; - } - - if(array_key_exists('theme', App::$layout) && App::$layout['theme']) - $page_theme = App::$layout['theme']; - - // Allow folks to over-rule channel themes and always use their own on their own site. - // The default is for channel themes to take precedence over your own on pages belonging - // to that channel. - - if($page_theme && local_channel() && App::$profile_uid && local_channel() != App::$profile_uid) { - if(get_pconfig(local_channel(),'system','always_my_theme')) - $page_theme = null; - } - - $is_mobile = App::$is_mobile || App::$is_tablet; - - $standard_system_theme = ((isset(App::$config['system']['theme'])) ? App::$config['system']['theme'] : ''); - $standard_theme_name = ((isset($_SESSION) && x($_SESSION,'theme')) ? $_SESSION['theme'] : $standard_system_theme); - - if($is_mobile) { - if(isset($_SESSION['show_mobile']) && !$_SESSION['show_mobile']) { - $system_theme = $standard_system_theme; - $theme_name = $standard_theme_name; - } - else { - $system_theme = ((isset(App::$config['system']['mobile_theme'])) ? App::$config['system']['mobile_theme'] : ''); - $theme_name = ((isset($_SESSION) && x($_SESSION,'mobile_theme')) ? $_SESSION['mobile_theme'] : $system_theme); - - if($theme_name === '' || $theme_name === '---' ) { - // user has selected to have the mobile theme be the same as the normal one - $system_theme = $standard_system_theme; - $theme_name = $standard_theme_name; - } - } - } - else { - $system_theme = $standard_system_theme; - $theme_name = $standard_theme_name; - - if($page_theme) - $theme_name = $page_theme; - } - - if($theme_name && - (file_exists('view/theme/' . $theme_name . '/css/style.css') || - file_exists('view/theme/' . $theme_name . '/php/style.php'))) - return($theme_name); - - foreach($app_base_themes as $t) { - if(file_exists('view/theme/' . $t . '/css/style.css') || - file_exists('view/theme/' . $t . '/php/style.php')) - return($t); - } - - $fallback = array_merge(glob('view/theme/*/css/style.css'),glob('view/theme/*/php/style.php')); - if(count($fallback)) - return (str_replace('view/theme/','', substr($fallback[0],0,-10))); - -} - - -/** - * @brief Return full URL to theme which is currently in effect. - * - * Provide a sane default if nothing is chosen or the specified theme does not exist. - * - * @param bool $installing default false - * - * @return string - */ -function current_theme_url($installing = false) { - global $a; - - $t = current_theme(); - - $opts = ''; - $opts = ((App::$profile_uid) ? '?f=&puid=' . App::$profile_uid : ''); - $opts .= ((x(App::$layout,'schema')) ? '&schema=' . App::$layout['schema'] : ''); - if(file_exists('view/theme/' . $t . '/php/style.php')) - return('view/theme/' . $t . '/php/style.pcss' . $opts); - - return('view/theme/' . $t . '/css/style.css'); -} - /** * @brief Check if current user has admin role. * @@ -1935,8 +1969,11 @@ function current_theme_url($installing = false) { * * @return bool true if user is an admin */ + function is_site_admin() { - $a = get_app(); + + if(! session_id()) + return false; if($_SESSION['delegate']) return false; @@ -1957,7 +1994,10 @@ function is_site_admin() { * @return bool true if user is a developer */ function is_developer() { - $a = get_app(); + + if(! session_id()) + return false; + if((intval($_SESSION['authenticated'])) && (is_array(App::$account)) && (App::$account['account_roles'] & ACCOUNT_ROLE_DEVELOPER)) @@ -1968,7 +2008,6 @@ function is_developer() { function load_contact_links($uid) { - $a = get_app(); $ret = array(); @@ -1977,7 +2016,7 @@ function load_contact_links($uid) { // logger('load_contact_links'); - $r = q("SELECT abook_id, abook_flags, abook_my_perms, abook_their_perms, xchan_hash, xchan_photo_m, xchan_name, xchan_url from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d ", + $r = q("SELECT abook_id, abook_flags, abook_my_perms, abook_their_perms, xchan_hash, xchan_photo_m, xchan_name, xchan_url, xchan_network from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d ", intval($uid) ); if($r) { @@ -2000,6 +2039,7 @@ function load_contact_links($uid) { * * @return string */ + function build_querystring($params, $name = null) { $ret = ''; foreach($params as $key => $val) { @@ -2042,8 +2082,9 @@ function dba_timer() { /** * @brief Returns xchan_hash from the observer. * - * @return string Empty if no observer, otherwise xchan_hash from observer + * @return empty string if no observer, otherwise xchan_hash from observer */ + function get_observer_hash() { $observer = App::get_observer(); if(is_array($observer)) @@ -2097,7 +2138,8 @@ function get_custom_nav(&$a, $navname) { * @param App &$a global application object */ function load_pdl(&$a) { - require_once('include/comanche.php'); + + App::$comanche = new Zotlabs\Render\Comanche(); if (! count(App::$layout)) { @@ -2106,7 +2148,7 @@ function load_pdl(&$a) { $layout = $arr['layout']; $n = 'mod_' . App::$module . '.pdl' ; - $u = comanche_get_channel_id(); + $u = App::$comanche->get_channel_id(); if($u) $s = get_pconfig($u, 'system', $n); if(! $s) @@ -2115,19 +2157,16 @@ function load_pdl(&$a) { if((! $s) && (($p = theme_include($n)) != '')) $s = @file_get_contents($p); if($s) { - comanche_parser($a, $s); + App::$comanche->parse($s); App::$pdl = $s; } } - } function exec_pdl(&$a) { - require_once('include/comanche.php'); - if(App::$pdl) { - comanche_parser($a, App::$pdl,1); + App::$comanche->parse(App::$pdl,1); } } @@ -2161,7 +2200,9 @@ function construct_page(&$a) { } } - if (($p = theme_include(current_theme() . '.js')) != '') + $current_theme = Zotlabs\Render\Theme::current(); + + if (($p = theme_include($current_theme[0] . '.js')) != '') head_add_js($p); if (($p = theme_include('mod_' . App::$module . '.php')) != '') @@ -2175,14 +2216,14 @@ function construct_page(&$a) { head_add_css(((x(App::$page, 'template')) ? App::$page['template'] : 'default' ) . '.css'); head_add_css('mod_' . App::$module . '.css'); - head_add_css(current_theme_url($installing)); + head_add_css(Zotlabs\Render\Theme::url($installing)); head_add_js('mod_' . App::$module . '.js'); App::build_pagehead(); if(App::$page['pdl_content']) { - App::$page['content'] = comanche_region($a,App::$page['content']); + App::$page['content'] = App::$comanche->region(App::$page['content']); } // Let's say we have a comanche declaration '[region=nav][/region][region=content]$nav $content[/region]'. @@ -2203,7 +2244,7 @@ function construct_page(&$a) { foreach(App::$layout as $k => $v) { if((strpos($k, 'region_') === 0) && strlen($v)) { if(strpos($v, '$region_') !== false) { - $v = preg_replace_callback('/\$region_([a-zA-Z0-9]+)/ism', 'comanche_replace_region', $v); + $v = preg_replace_callback('/\$region_([a-zA-Z0-9]+)/ism', array(App::$comanche,'replace_region'), $v); } // And a couple of convenience macros @@ -2241,6 +2282,12 @@ function construct_page(&$a) { $page = App::$page; $profile = App::$profile; + // There's some experimental support for right-to-left text in the view/php/default.php page template. + // In v1.9 we started providing direction preference in the per language hstrings.php file + // This requires somebody with fluency in a RTL language to make happen + + $page['direction'] = 0; // ((App::$rtl) ? 1 : 0); + header("Content-type: text/html; charset=utf-8"); // security headers - see https://securityheaders.io @@ -2281,7 +2328,6 @@ function appdirpath() { * @param string $icon */ function head_set_icon($icon) { - global $a; App::$data['pageicon'] = $icon; // logger('head_set_icon: ' . $icon); @@ -2293,7 +2339,6 @@ function head_set_icon($icon) { * @return string absolut path to pageicon */ function head_get_icon() { - global $a; $icon = App::$data['pageicon']; if(! strpos($icon, '://')) @@ -2359,7 +2404,7 @@ function z_get_temp_dir() { } function z_check_cert() { - $a = get_app(); + if(strpos(z_root(),'https://') !== false) { $x = z_fetch_url(z_root() . '/siteinfo/json'); if(! $x['success']) { @@ -2380,8 +2425,6 @@ function z_check_cert() { */ function cert_bad_email() { - $a = get_app(); - $email_tpl = get_intltext_template("cert_bad_eml.tpl"); $email_msg = replace_macros($email_tpl, array( '$sitename' => App::$config['system']['sitename'], @@ -2402,26 +2445,32 @@ function cert_bad_email() { */ function check_cron_broken() { - $t = get_config('system','lastpollcheck'); + $d = get_config('system','lastcron'); + + if((! $d) || ($d < datetime_convert('UTC','UTC','now - 4 hours'))) { + Zotlabs\Daemon\Master::Summon(array('Cron')); + set_config('system','lastcron',datetime_convert()); + } + + $t = get_config('system','lastcroncheck'); if(! $t) { // never checked before. Start the timer. - set_config('system','lastpollcheck',datetime_convert()); + set_config('system','lastcroncheck',datetime_convert()); return; } + if($t > datetime_convert('UTC','UTC','now - 3 days')) { // Wait for 3 days before we do anything so as not to swamp the admin with messages return; } - $d = get_config('system','lastpoll'); + set_config('system','lastcroncheck',datetime_convert()); + if(($d) && ($d > datetime_convert('UTC','UTC','now - 3 days'))) { // Scheduled tasks have run successfully in the last 3 days. - set_config('system','lastpollcheck',datetime_convert()); return; } - $a = get_app(); - $email_tpl = get_intltext_template("cron_bad_eml.tpl"); $email_msg = replace_macros($email_tpl, array( '$sitename' => App::$config['system']['sitename'], @@ -2435,8 +2484,16 @@ function check_cron_broken() { 'From: Administrator' . '@' . App::get_hostname() . "\n" . 'Content-type: text/plain; charset=UTF-8' . "\n" . 'Content-transfer-encoding: 8bit' ); - set_config('system','lastpollcheck',datetime_convert()); return; } + +function observer_prohibited($allow_account = false) { + + if($allow_account) + return (((get_config('system','block_public')) && (! get_account_id()) && (! remote_channel())) ? true : false ); + return (((get_config('system','block_public')) && (! local_channel()) && (! remote_channel())) ? true : false ); + +} + diff --git a/doc/Privacy.md b/doc/Privacy.md index 511293c52..089977d7e 100644 --- a/doc/Privacy.md +++ b/doc/Privacy.md @@ -42,11 +42,11 @@ You MAY additionally provide other profile information. Any information which yo Content you provide (status posts, photos, files, etc.) belongs to you. The $Projectname default is to publish content openly and visible to anybody on the internet (PUBLIC). You MAY control this in your channel settings and restrict the default permissions or you MAY restrict the visibility of any single published item separately (PRIVATE). $Projectname developers will ensure that restricted content is ONLY visible to those in the restriction list - to the best of their ability. -Content (especially status posts) that you share with other networks or that you have made visible to anybody on the internet (PUBLIC) cannot easily be taken back once it has been published. It MAY be shared with other networks and made available through RSS/Atom feeds. It may also be syndicated on other $Projectname sites. It MAY appear on spy networks and internet searches. If you do not wish this default behaviour please adjust your channel settings and restrict who can see your content. +Content (especially status posts) that you share with other networks or that you have made visible to anybody on the internet (PUBLIC) cannot easily be taken back once it has been published. It MAY be shared with other networks and made available through RSS/Atom feeds. It may also be syndicated on other $Projectname sites. It MAY appear on other networks and websites and be visible in internet searches. If you do not wish this default behaviour please adjust your channel settings and restrict who can see your content. **Comments and Forum posts** -Comments to posts that were created by others and posts which are designated as forum posts belong to you as the creator/author, but the distribution of these posts is not under your direct control. These posts/comments MAY be re-distributed to others, and MAY be visible to anybody on the internet. In the case of comments, the creator of the "first message" in the thread to which you are replying controls the distribution of all comments and replies to that message. +Comments to posts that were created by others and posts which are designated as forum posts belong to you as the creator/author, but the distribution of these posts is not under your direct control, and you relinquish SOME rights to these items. These posts/comments MAY be re-distributed to others, and MAY be visible to anybody on the internet. In the case of comments, the creator of the "first message" in the thread (conversation) to which you are replying controls the distribution of all comments and replies to that message. They "own" and therefore have certain rights with regard to the entire conversation (including all comments contained within it). You can still edit or delete the comment, but the conversation owner also has rights to edit, delete, re-distribute, and backup/restore any or all the content from the conversation. **Private Information** diff --git a/doc/Webpages.md b/doc/Webpages.md index dafd3661d..801a9a3a0 100644 --- a/doc/Webpages.md +++ b/doc/Webpages.md @@ -1,7 +1,7 @@ Creating Webpages ================= -Red enables users to create static webpages. To activate this feature, enable the web pages feature in your Additional Features section. +Hubzilla enables users to create static webpages. To activate this feature, enable the web pages feature in your Additional Features section. Once enabled, a new tab will appear on your channel page labelled "Webpages". Clicking this link will take you to the webpage editor. Here you can create a post using either BBCode or the rich text editor. diff --git a/doc/acl_dialog_post.html b/doc/acl_dialog_post.html new file mode 100644 index 000000000..80b2c68b6 --- /dev/null +++ b/doc/acl_dialog_post.html @@ -0,0 +1,16 @@ + + +

    Post Permissions

    + +

    The permissions dialog lets you select which channels and/or privacy groups can see the post. You can also select who is explicitly denied access. For example, say you are planning a surprise party for a friend. You can send an invitation post to everyone in your Friends group except the friend you are surprising. In this case you "Show" the Friends group but "Don't show" that one person.

    + +
    +
    Tip! +
    The border color of each channel indicates whether that channel — or one of the groups it's a member of — will have access to the post. The border color will also indicate when a channel, or group it belongs to, has been explicitly set to "Don't show".
    +
    + +

    Why can't I edit a post's permissions after I saved it?

    + +

    You are able to change permissons to your files, photos and the likes, but not to posts after you have saved them. The main reason is: Once you have saved a post it is being distributed either to the public channel and from there to other Hubzilla servers or to those you intended it to go. Just like you cannot reclaim something you gave to another person, you cannot change permissions to Hubzilla posts. We would need to track everywhere your posting goes, keep track of everyone you allowed to see it and then keep track of from whom to delete it.

    +

    If a posting is public this is even harder, as the Hubzilla is a global network and there is no way to follow a post, let alone reclaim it reliably. Other networks that may receive your post have no reliable way to delete or reclaim the post.

    \ No newline at end of file diff --git a/doc/addons.bb b/doc/addons.bb index 67282521f..b83b3276a 100644 --- a/doc/addons.bb +++ b/doc/addons.bb @@ -2,10 +2,12 @@ [list=1] [*] abcjsplugin - Create musical scores in your posts [*] adultphotoflag - prevents nsfw photos from being displayed in public albums +[*] b2tbtn - provide button to go directly to top of page if you are scrolled a long way down [*] bbmath - use complex math expressions in your posts [*] bookmarker - replace #^ with bookmark link in posts [*] buglink - provide a bug reporting icon in the lower-left corner of every page [*] calc - a scientific calculator +[*] cdav - CalDAV/CardDAV server [*] chess - cross domain identity aware interactive chess games [*] chords - generate fingering charts and alternatives for every known guitar chord [*] custom_home - set a custom page as the hub start page @@ -13,18 +15,24 @@ [*] diaspora - Diaspora protocol emulator [*] diaspost - crosspost to a Diaspora account (different from the Diaspora protocol emulator) [*] dirstats - show some interesting statistics generated by the driectory server +[*] docs - alternate documentation pages [*] donate - provides a project donation page [*] dwpost - crosspost to Dreamwidth +[*] embedphotos - tool to embed photos from your albums in a post [*] extcron - use an external cron service to run your hub's scheduled tasks [*] flattrwidget - provides a "Flattr Us" button [*] flip - create upside down text [*] fortunate - displays random quote (fortune cookie). Requires setting up a fortune server. +[*] friendica - Friendica (DFRN) protocol. Under development. [*] frphotos - import photo albums from Friendica +[*] gnusoc - GNU-Social (OStatus) protocol. Under development. [*] hexit - headecimal conversion tool +[*] hubwall - send an admin email to all hub accounts [*] ijpost - crosspost to Insanejournal [*] irc - connect to IRC chatrooms [*] jappixmini - XMPP chat -[*] jsupload - (recommended) upload multiple photos to photo albums at once. +[*] jsupload - upload multiple photos to photo albums at once. +[*] keepout - prevents nearly all use of site when not logged in, more restrictive than 'block public' setting [*] ldapauth - login via account on LDAP or Windows Active Directory domain [*] libertree - crosspost to Libertree [*] likebanner - create a "like us on red#matrix" banner image @@ -32,6 +40,7 @@ [*] logrot - logfile rotation utility [*] mahjongg - Chinese puzzle game [*] mailhost - when using multiple channel clones, select one to receive email notifications +[*] metatag - provide SEO friendly pages [*] mayan_places - set location field to a random city in the Mayan world [*] morechoice - additional gender/sexual-preference choices for profiles (not safe for work) [*] moremoods - Additional mood options @@ -60,6 +69,7 @@ [*] startpage - set a personal preferred page to redirect after logging in. [*] statistics_json - Diaspora statistics generator [*] statusnet - GNU-social and StatusNet crosspost [zrl=[baseurl]/help/addons_gnusocial]Posting To Gnu Social[/zrl] +[*] std_embeds - allow unfiltered embeds for popular providers like youtube, vimeo and soundcloud [*] superblock - Highly recommended - completely block an offensive channel from your stream [*] testdrive - Turns your hub into a test drive site with accounts that expire after a trail period. [*] tictac - 3D tic-tac-toe @@ -76,6 +86,13 @@ [h3]Addon Repositories[/h3] +We [b]strongly recommend[/b] that authors of addons publish/submit them to the project addon repository. This has several advantages. Project developers can easily fix security flaws and make changes to comply with recent changes in core code. Addons provided in third-party repositories are considered untrusted. If the project core code changes in an incompatible way, there may be no alternative but to physically remove or rename the addon files in order to get your site working again. Often only the plugin/addon author can help you regain control of your website, and project developers are unable to assist you; because by definition your site configuration has been modified in ways that we cannot easily test or verify. + +For these reasons we [b]strongly recommend[/b] that you do NOT install addons from third-party repositories. + +We also recognise that some developers prefer working on their own and do not wish their code to be mingled with the project repository for a variety of reasons. These developers can ease troubleshooting and debugging by providing a README file in their respective code repository outlining the process for submitting patches and bug fixes. It is also recommended that these projects provide both a 'dev' (development) and 'master' (production) branch which tracks the current project branches of those names. This is because dev and master are often not compatible from the viewpoint of library interfaces. It is also highly recommended that your repository versions are tagged and moved forward within 24 hours of project releases. This is a major inconvenience for everybdy involved, and can present downtime for production sites while this process is being carried out; which is one more reason why we [b]strongly recommend[/b] that addons be submitted to the project addon repository and that you do NOT install such third-party addons. + + [url=https://github.com/redmatrix/hubzilla-addons]https://github.com/redmatrix/hubzilla-addons[/url] Main project addon repository [url=https://github.com/23n/red-addons]https://github.com/23n/red-addons[/url] Oliver's repository (mayan_places and flip) diff --git a/doc/admins.bb b/doc/admins.bb index 484212024..6c78ce999 100644 --- a/doc/admins.bb +++ b/doc/admins.bb @@ -1,15 +1,15 @@ [h2]Documentation for Hub Administrators[/h2] - -[h3]Administrators[/h3] - +[h3]Deploying your hub[/h3] [zrl=[baseurl]/help/install]Install[/zrl] [zrl=[baseurl]/help/red2pi]Installing $Projectname on the Raspberry Pi[/zrl] [zrl=[baseurl]/help/Hubzilla_on_OpenShift]$Projectname on OpenShift[/zrl] +[h3]Taking care of your hub[/h3] [zrl=[baseurl]/help/troubleshooting]Troubleshooting Tips[/zrl] +[zrl=[baseurl]/help/theme_management]Theme Management[/zrl] [zrl=[baseurl]/help/hidden_configs]Tweaking $Projectname's Hidden Configurations[/zrl] -[zrl=[baseurl]/help/faq_admins]FAQ For Admins[/zrl] [zrl=[baseurl]/help/service_classes]Service Classes[/zrl] [zrl=[baseurl]/help/directories]Working with and configuring Directories[/zrl] -[zrl=[baseurl]/help/theme_management]Theme Management[/zrl] - +[h3]Frequently asked questions[/h3] +[zrl=[baseurl]/help/faq_admins]FAQ For Admins[/zrl] +#include doc/macros/main_footer.bb; diff --git a/doc/bbcode.html b/doc/bbcode.html index 3e9bda1d9..7a2c969eb 100644 --- a/doc/bbcode.html +++ b/doc/bbcode.html @@ -14,6 +14,7 @@
  • [img float=right]https://zothub.com/images/default_profile_photos/rainbow_man/48.jpg[/img] Image/photo
  • [code]code[/code] code
    +
  • [code=xxx]syntax highlighted code[/code] supported languages php, css, mysql, sql, abap, diff, html, perl, ruby, vbscript, avrc, dtd, java, xml, cpp, python, javascript, js, json, sh
  • [quote]quote[/quote]
    quote

  • [quote=Author]Author? Me? No, no, no...[/quote]
    Author wrote:
    Author? Me? No, no, no...

  • [nobb] may be used to escape bbcode.
    @@ -27,12 +28,31 @@
  • [list=a]
  • [list=A]
  • [ul]
    -
  • [ol] +
  • [ol]
    +
  • [dl]
    +
  • [dl terms="biumlh"] — where style of the terms can be any combination of: +
    +
    b
    bold
    +
    i
    italic
    +
    u
    underline
    +
    m
    monospace
    +
    l
    large
    +
    h
    horizontal — like this defintion list
    +
    +
  • For example:
    [ul]
    [*] First list element
    [*] Second list element
    [/ul]

    Will render something like:
    • First list element
      -
    • Second list element

    +
  • Second list element + +or

    [dl terms="b"]
    [*= First element term] First element description
    [*= Second element term] Second element description
    [/dl]

    Will render something like:

    +
    +
    First element term
    First element description
    +
    Second element term
    Second element description
    +

    + +
    There's also: