diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/Contact.php | 13 | ||||
-rw-r--r-- | include/RedDAV/RedBrowser.php | 361 | ||||
-rw-r--r-- | include/auth.php | 127 | ||||
-rw-r--r-- | include/bbcode.php | 12 | ||||
-rw-r--r-- | include/cli_startup.php | 6 | ||||
-rw-r--r-- | include/config.php | 2 | ||||
-rw-r--r-- | include/conversation.php | 6 | ||||
-rwxr-xr-x | include/diaspora.php | 77 | ||||
-rw-r--r-- | include/follow.php | 2 | ||||
-rw-r--r-- | include/hubloc.php | 6 | ||||
-rw-r--r-- | include/identity.php | 41 | ||||
-rwxr-xr-x | include/items.php | 10 | ||||
-rw-r--r-- | include/message.php | 2 | ||||
-rw-r--r-- | include/nav.php | 12 | ||||
-rw-r--r-- | include/permissions.php | 5 | ||||
-rw-r--r-- | include/photo/photo_driver.php | 63 | ||||
-rwxr-xr-x | include/plugin.php | 1 | ||||
-rw-r--r-- | include/poller.php | 7 | ||||
-rw-r--r-- | include/reddav.php | 486 | ||||
-rw-r--r-- | include/statistics_fns.php | 73 | ||||
-rw-r--r-- | include/text.php | 36 | ||||
-rw-r--r-- | include/widgets.php | 2 | ||||
-rw-r--r-- | include/zot.php | 2 |
23 files changed, 870 insertions, 482 deletions
diff --git a/include/Contact.php b/include/Contact.php index 8d50b1e5b..4440369dc 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -328,6 +328,19 @@ function mark_orphan_hubsxchans() { intval(HUBLOC_OFFLINE) ); +// $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 = '') order by rand()", +// dbesc($realm) +// ); +// } +// else { +// $r = q("select * from site where site_access != 0 and site_register !=0 and site_realm = '%s' order by rand()", +// dbesc($realm) +// ); +// } + + $r = q("select hubloc_id, hubloc_hash from hubloc where (hubloc_status & %d) and not (hubloc_flags & %d)", intval(HUBLOC_OFFLINE), intval(HUBLOC_FLAGS_ORPHANCHECK) diff --git a/include/RedDAV/RedBrowser.php b/include/RedDAV/RedBrowser.php new file mode 100644 index 000000000..dcd54ef82 --- /dev/null +++ b/include/RedDAV/RedBrowser.php @@ -0,0 +1,361 @@ +<?php +/** + * RedMatrix - "The Network" + * + * @link http://github.com/friendica/red + * @license http://opensource.org/licenses/mit-license.php The MIT License (MIT) + */ + +namespace RedMatrix\RedDAV; + +use Sabre\DAV; + +/** + * @brief Provides a DAV frontend for the webbrowser. + * + * RedBrowser is a SabreDAV server-plugin to provide a view to the DAV storage + * for the webbrowser. + * + * @extends \Sabre\DAV\Browser\Plugin + */ +class RedBrowser extends DAV\Browser\Plugin { + + /** + * @see set_writeable() + * @see \Sabre\DAV\Auth\Backend\BackendInterface + * @var RedBasicAuth + */ + private $auth; + + /** + * @brief Constructor for RedBrowser class. + * + * $enablePost will be activated through set_writeable() in a later stage. + * At the moment the write_storage permission is only valid for the whole + * folder. No file specific permissions yet. + * + * Disable assets with $enableAssets = false. Should get some thumbnail views + * anyway. + * + * @param RedBasicAuth &$auth + */ + public function __construct(&$auth) { + $this->auth = $auth; + parent::__construct(false, false); + } + + /** + * The DAV browser is instantiated after the auth module and directory classes + * but before we know the current directory and who the owner and observer + * are. So we add a pointer to the browser into the auth module and vice versa. + * Then when we've figured out what directory is actually being accessed, we + * call the following function to decide whether or not to show web elements + * which include writeable objects. + * + * @todo Maybe this can be solved with some $server->subscribeEvent()? + */ + public function set_writeable() { + if (! $this->auth->owner_id) { + $this->enablePost = false; + } + + if (! perm_is_allowed($this->auth->owner_id, get_observer_hash(), 'write_storage')) { + $this->enablePost = false; + } else { + $this->enablePost = true; + } + } + + /** + * @brief Creates the directory listing for the given path. + * + * @param string $path which should be displayed + */ + public function generateDirectoryIndex($path) { + // (owner_id = channel_id) is visitor owner of this directory? + $is_owner = ((local_user() && $this->auth->owner_id == local_user()) ? true : false); + + if ($this->auth->getTimezone()) + date_default_timezone_set($this->auth->getTimezone()); + + require_once('include/conversation.php'); + + if ($this->auth->owner_nick) { + $html = profile_tabs(get_app(), (($is_owner) ? true : false), $this->auth->owner_nick); + } + + $files = $this->server->getPropertiesForPath($path, array( + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ), 1); + + $parent = $this->server->tree->getNodeForPath($path); + + $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); + + $parentpath['icon'] = $this->enableAssets ? '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="' . t('parent') . '"></a>' : ''; + $parentpath['path'] = $fullPath; + } + + $f = array(); + foreach ($files as $file) { + $ft = array(); + $type = null; + + // This is the current directory, we can skip it + if (rtrim($file['href'],'/') == $path) continue; + + list(, $name) = DAV\URLUtil::splitPath($file['href']); + + if (isset($file[200]['{DAV:}resourcetype'])) { + $type = $file[200]['{DAV:}resourcetype']->getValue(); + + // resourcetype can have multiple values + if (!is_array($type)) $type = array($type); + + foreach ($type as $k=>$v) { + // Some name mapping is preferred + switch ($v) { + case '{DAV:}collection' : + $type[$k] = t('Collection'); + break; + case '{DAV:}principal' : + $type[$k] = t('Principal'); + break; + case '{urn:ietf:params:xml:ns:carddav}addressbook' : + $type[$k] = t('Addressbook'); + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $type[$k] = t('Calendar'); + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : + $type[$k] = t('Schedule Inbox'); + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : + $type[$k] = t('Schedule Outbox'); + break; + case '{http://calendarserver.org/ns/}calendar-proxy-read' : + $type[$k] = 'Proxy-Read'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-write' : + $type[$k] = 'Proxy-Write'; + break; + } + } + $type = implode(', ', $type); + } + + // If no resourcetype was found, we attempt to use + // the contenttype property + if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { + $type = $file[200]['{DAV:}getcontenttype']; + } + if (!$type) $type = t('Unknown'); + + $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, '/')); + + $displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name; + + $displayName = $this->escapeHTML($displayName); + $type = $this->escapeHTML($type); + + $icon = ''; + if ($this->enableAssets) { + $node = $this->server->tree->getNodeForPath(($path ? $path . '/' : '') . $name); + foreach (array_reverse($this->iconMap) as $class=>$iconName) { + if ($node instanceof $class) { + $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24"></a>'; + break; + } + } + } + + $parentHash = ""; + $owner = $this->auth->owner_id; + $splitPath = split("/", $fullPath); + if (count($splitPath) > 3) { + for ($i = 3; $i < count($splitPath); $i++) { + $attachName = urldecode($splitPath[$i]); + $attachHash = $this->findAttachHash($owner, $parentHash, $attachName); + $parentHash = $attachHash; + } + } + + $attachIcon = ""; // "<a href=\"attach/".$attachHash."\" title=\"".$displayName."\"><i class=\"icon-download\"></i></a>"; + + // put the array for this file together + $ft['attachId'] = $this->findAttachIdByHash($attachHash); + $ft['fileStorageUrl'] = substr($fullPath, 0, strpos($fullPath, "cloud/")) . "filestorage/" . $this->auth->getCurrentUser(); + $ft['icon'] = $icon; + $ft['attachIcon'] = (($size) ? $attachIcon : ''); + // @todo Should this be an item value, not a global one? + $ft['is_owner'] = $is_owner; + $ft['fullPath'] = $fullPath; + $ft['displayName'] = $displayName; + $ft['type'] = $type; + $ft['size'] = $size; + $ft['sizeFormatted'] = $this->userReadableSize($size); + $ft['lastmodified'] = (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(), $lastmodified) : ''); + + $f[] = $ft; + } + + // Storage and quota for the account (all channels of the owner of this directory)! + $limit = 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) + ); + $used = $r[0]['total']; + if ($used) { + $quotaDesc = t('%1$s used'); + $quotaDesc = sprintf($quotaDesc, + $this->userReadableSize($used)); + } + if ($limit && $used) { + $quotaDesc = t('%1$s used of %2$s (%3$s%)'); + $quotaDesc = sprintf($quotaDesc, + $this->userReadableSize($used), + $this->userReadableSize($limit), + round($used / $limit, 1)); + } + + // prepare quota for template + $quota['used'] = $used; + $quota['limit'] = $limit; + $quota['desc'] = $quotaDesc; + + $html .= replace_macros(get_markup_template('cloud_directory.tpl'), array( + '$header' => t('Files') . ": " . $this->escapeHTML($path) . "/", + '$parentpath' => $parentpath, + '$entries' => $f, + '$quota' => $quota, + '$name' => t('Name'), + '$type' => t('Type'), + '$size' => t('Size'), + '$lastmod' => t('Last Modified'), + '$parent' => t('parent'), + '$edit' => t('Edit'), + '$delete' => t('Delete'), + '$total' => t('Total') + )); + + $output = ''; + if ($this->enablePost) { + $this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output)); + } + $html .= $output; + + get_app()->page['content'] = $html; + construct_page(get_app()); + } + + function userReadableSize($size) { + $ret = ""; + if (is_numeric($size)) { + $incr = 0; + $k = 1024; + $unit = array('bytes', 'KB', 'MB', 'GB', 'TB', 'PB'); + while (($size / $k) >= 1){ + $incr++; + $size = round($size / $k, 2); + } + $ret = $size . " " . $unit[$incr]; + } + return $ret; + } + + /** + * @brief Creates a form to add new folders and upload files. + * + * @param \Sabre\DAV\INode $node + * @param string &$output + */ + public function htmlActionsPanel(DAV\INode $node, &$output) { + if (! $node instanceof DAV\ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') + return; + + $output .= replace_macros(get_markup_template('cloud_actionspanel.tpl'), array( + '$folder_header' => t('Create new folder'), + '$folder_submit' => t('Create'), + '$upload_header' => t('Upload file'), + '$upload_submit' => t('Upload') + )); + } + + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + return z_root() . '/cloud/?sabreAction=asset&assetName=' . urlencode($assetName); + } + + /** + * @brief Return the hash of an attachment. + * + * Given the owner, the parent folder and and attach name get the attachment + * hash. + * + * @param int $owner + * The owner_id + * @param string $hash + * The parent's folder hash + * @param string $attachName + * The name of the attachment + * @return string + */ + protected function findAttachHash($owner, $parentHash, $attachName) { + $r = q("SELECT hash FROM attach WHERE uid = %d AND folder = '%s' AND filename = '%s' ORDER BY edited DESC LIMIT 1", + intval($owner), + dbesc($parentHash), + dbesc($attachName) + ); + $hash = ""; + if ($r) { + foreach ($r as $rr) { + $hash = $rr['hash']; + } + } + return $hash; + } + + /** + * @brief Returns an attachment's id for a given hash. + * + * This id is used to access the attachment in filestorage/ + * + * @param string $attachHash + * The hash of an attachment + * @return string + */ + protected function findAttachIdByHash($attachHash) { + $r = q("SELECT id FROM attach WHERE hash = '%s' ORDER BY edited DESC LIMIT 1", + dbesc($attachHash) + ); + $id = ""; + if ($r) { + foreach ($r as $rr) { + $id = $rr['id']; + } + } + return $id; + } +}
\ No newline at end of file diff --git a/include/auth.php b/include/auth.php index f8188f443..8f68fc562 100644 --- a/include/auth.php +++ b/include/auth.php @@ -1,11 +1,23 @@ -<?php /** @file */ - +<?php +/** + * @file include/auth.php + * @brief Functions and inline functionality for authentication. + * + * This file provides some functions for authentication handling and inline + * functionality. Look for auth parameters or re-validate an existing session + * also handles logout. + * Also provides a function for OpenID identiy matching. + */ require_once('include/security.php'); +/** + * @brief Resets the current session. + * + * @return void + */ function nuke_session() { - - new_cookie(0); + new_cookie(0); // 0 means delete on browser exit unset($_SESSION['authenticated']); unset($_SESSION['account_id']); @@ -27,21 +39,24 @@ function nuke_session() { } /** - * Verify login credentials - * - * Returns account record on success, null on failure + * @brief Verify login credentials. * + * @param string $email + * The email address to verify. + * @param string $pass + * The provided password to verify. + * @return array|null + * Returns account record on success, null on failure. */ +function account_verify_password($email, $pass) { -function account_verify_password($email,$pass) { - - $email_verify = get_config('system','verify_email'); - $register_policy = get_config('system','register_policy'); + $email_verify = get_config('system', 'verify_email'); + $register_policy = get_config('system', 'register_policy'); // Currently we only verify email address if there is an open registration policy. // This isn't because of any policy - it's because the workflow gets too complicated if // you have to verify the email and then go through the account approval workflow before - // letting them login. + // letting them login. if(($email_verify) && ($register_policy == REGISTER_OPEN) && ($record['account_flags'] & ACCOUNT_UNVERIFIED)) return null; @@ -51,9 +66,10 @@ function account_verify_password($email,$pass) { ); if(! ($r && count($r))) return null; + foreach($r as $record) { if(($record['account_flags'] == ACCOUNT_OK) - && (hash('whirlpool',$record['account_salt'] . $pass) === $record['account_password'])) { + && (hash('whirlpool', $record['account_salt'] . $pass) === $record['account_password'])) { logger('password verified for ' . $email); return $record; } @@ -61,7 +77,6 @@ function account_verify_password($email,$pass) { $error = 'password failed for ' . $email; logger($error); - if($record['account_flags'] & ACCOUNT_UNVERIFIED) logger('Account is unverified. account_flags = ' . $record['account_flags']); if($record['account_flags'] & ACCOUNT_BLOCKED) @@ -88,14 +103,12 @@ function account_verify_password($email,$pass) { * also handles logout */ - -if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-params'))) || ($_POST['auth-params'] !== 'login'))) { - +if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) && + ((! (x($_POST, 'auth-params'))) || ($_POST['auth-params'] !== 'login'))) { // process a logout request - if(((x($_POST,'auth-params')) && ($_POST['auth-params'] === 'logout')) || ($a->module === 'logout')) { - + if(((x($_POST, 'auth-params')) && ($_POST['auth-params'] === 'logout')) || ($a->module === 'logout')) { // process logout request $args = array('channel_id' => local_user()); call_hooks('logging_out', $args); @@ -106,16 +119,16 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p // re-validate a visitor, optionally invoke "su" if permitted to do so - if(x($_SESSION,'visitor_id') && (! x($_SESSION,'uid'))) { + if(x($_SESSION, 'visitor_id') && (! x($_SESSION, 'uid'))) { // if our authenticated guest is allowed to take control of the admin channel, make it so. - $admins = get_config('system','remote_admin'); - if($admins && is_array($admins) && in_array($_SESSION['visitor_id'],$admins)) { + $admins = get_config('system', 'remote_admin'); + if($admins && is_array($admins) && in_array($_SESSION['visitor_id'], $admins)) { $x = q("select * from account where account_email = '%s' and account_email != '' and ( account_flags & %d ) limit 1", - dbesc(get_config('system','admin_email')), + dbesc(get_config('system', 'admin_email')), intval(ACCOUNT_ROLE_ADMIN) ); if($x) { - new_cookie(60*60*24); // one day + new_cookie(60 * 60 * 24); // one day $_SESSION['last_login_date'] = datetime_convert(); unset($_SESSION['visitor_id']); // no longer a visitor authenticate_success($x[0], true, true); @@ -137,20 +150,19 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p // already logged in user returning - if(x($_SESSION,'uid') || x($_SESSION,'account_id')) { + if(x($_SESSION, 'uid') || x($_SESSION, 'account_id')) { // first check if we're enforcing that sessions can't change IP address - + // @todo what to do with IPv6 addresses if($_SESSION['addr'] && $_SESSION['addr'] != $_SERVER['REMOTE_ADDR']) { logger('SECURITY: Session IP address changed: ' . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']); - $partial1 = substr($_SESSION['addr'],0,strrpos($_SESSION['addr'],'.')); - $partial2 = substr($_SERVER['REMOTE_ADDR'],0,strrpos($_SERVER['REMOTE_ADDR'],'.')); + $partial1 = substr($_SESSION['addr'], 0, strrpos($_SESSION['addr'], '.')); + $partial2 = substr($_SERVER['REMOTE_ADDR'], 0, strrpos($_SERVER['REMOTE_ADDR'], '.')); - - $paranoia = intval(get_pconfig($_SESSION['uid'],'system','paranoia')); + $paranoia = intval(get_pconfig($_SESSION['uid'], 'system', 'paranoia')); if(! $paranoia) - $paranoia = intval(get_config('system','paranoia')); + $paranoia = intval(get_config('system', 'paranoia')); switch($paranoia) { case 0: @@ -158,8 +170,8 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p break; case 2: // check 2 octets - $partial1 = substr($partial1,0,strrpos($partial1,'.')); - $partial2 = substr($partial2,0,strrpos($partial2,'.')); + $partial1 = substr($partial1, 0, strrpos($partial1, '.')); + $partial2 = substr($partial2, 0, strrpos($partial2, '.')); if($partial1 == $partial2) break; case 1: @@ -169,12 +181,11 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p case 3: default: // check any difference at all - logger('Session address changed. Paranoid setting in effect, blocking session. ' + logger('Session address changed. Paranoid setting in effect, blocking session. ' . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']); nuke_session(); goaway(z_root()); break; - } } @@ -191,17 +202,15 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p if(strcmp(datetime_convert('UTC','UTC','now - 12 hours'), $_SESSION['last_login_date']) > 0 ) { $_SESSION['last_login_date'] = datetime_convert(); $login_refresh = true; - } - authenticate_success($r[0], false, false, false, $login_refresh); + } + authenticate_success($r[0], false, false, false, $login_refresh); } else { $_SESSION['account_id'] = 0; nuke_session(); goaway(z_root()); } - - } - + } // end logged in user returning } else { @@ -211,10 +220,10 @@ else { // handle a fresh login request - if((x($_POST,'password')) && strlen($_POST['password'])) - $encrypted = hash('whirlpool',trim($_POST['password'])); + if((x($_POST, 'password')) && strlen($_POST['password'])) + $encrypted = hash('whirlpool', trim($_POST['password'])); - if((x($_POST,'auth-params')) && $_POST['auth-params'] === 'login') { + if((x($_POST, 'auth-params')) && $_POST['auth-params'] === 'login') { $record = null; @@ -239,8 +248,7 @@ else { $record = $addon_auth['user_record']; } else { - - $record = get_app()->account = account_verify_password($_POST['username'],$_POST['password']); + $record = get_app()->account = account_verify_password($_POST['username'], $_POST['password']); if(get_app()->account) { $_SESSION['account_id'] = get_app()->account['account_id']; @@ -249,21 +257,20 @@ else { notice( t('Failed authentication') . EOL); } - logger('authenticate: ' . print_r(get_app()->account,true), LOGGER_DEBUG); - + logger('authenticate: ' . print_r(get_app()->account, true), LOGGER_DEBUG); } if((! $record) || (! count($record))) { $error = 'authenticate: failed login attempt: ' . notags(trim($_POST['username'])) . ' from IP ' . $_SERVER['REMOTE_ADDR']; logger($error); // Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention - $authlog = get_config('system', 'authlog'); - if ($authlog) - @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); + $authlog = get_config('system', 'authlog'); + if ($authlog) + @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); notice( t('Login failed.') . EOL ); goaway(z_root()); - } + } // If the user specified to remember the authentication, then change the cookie // to expire after one year (the default is when the browser is closed). @@ -293,11 +300,25 @@ else { } +/** + * @brief Returns the channel_id for a given openid_identity. + * + * Queries the values from pconfig configuration for the given openid_identity + * and returns the corresponding channel_id. + * + * @fixme How do we prevent that an OpenID identity is used more than once? + * + * @param string $authid + * The given openid_identity + * @return int|bool + * Return channel_id from pconfig or false. + */ function match_openid($authid) { - $r = q("select * from pconfig where cat = 'system' and k = 'openid' and v = '%s' limit 1", + // Query the uid/channel_id from pconfig for a given value. + $r = q("SELECT uid FROM pconfig WHERE cat = 'system' AND k = 'openid' AND v = '%s' LIMIT 1", dbesc($authid) ); if($r) return $r[0]['uid']; return false; -} +} diff --git a/include/bbcode.php b/include/bbcode.php index 0803ed365..d7a5ac457 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -159,6 +159,14 @@ function bb_parse_app($match) { } +function bb_parse_element($match) { + $j = json_decode(base64url_decode($match[1]),true); + if($j) { + $o = EOL . '<a href="' . z_root() . '" foo="baz" onclick="importElement(\'' . $match[1] . '\'); return false;" >' . t('Install design element: ') . $j['pagetitle'] . '</a>' . EOL; + } + return $o; +} + function bb_qr($match) { return '<img class="zrl" src="' . z_root() . '/photo/qr?f=&qr=' . urlencode($match[1]) . '" alt="' . t('QR code') . '" title="' . htmlspecialchars($match[1],ENT_QUOTES,'UTF-8') . '" />'; } @@ -700,6 +708,10 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace_callback("/\[app\](.*?)\[\/app\]/ism",'bb_parse_app', $Text); } + if(strpos($Text,'[/element]') !== false) { + $Text = preg_replace_callback("/\[element\](.*?)\[\/element\]/ism",'bb_parse_element', $Text); + } + // html5 video and audio if (strpos($Text,'[/video]') !== false) { diff --git a/include/cli_startup.php b/include/cli_startup.php index 6bd4e7520..f90a75cd1 100644 --- a/include/cli_startup.php +++ b/include/cli_startup.php @@ -6,7 +6,7 @@ require_once('boot.php'); function cli_startup() { - global $a, $db; + global $a, $db, $default_timezone; if(is_null($a)) { $a = new App; @@ -14,6 +14,10 @@ function cli_startup() { if(is_null($db)) { @include(".htconfig.php"); + + $a->timezone = ((x($default_timezone)) ? $default_timezone : 'UTC'); + date_default_timezone_set($a->timezone); + require_once('include/dba/dba_driver.php'); $db = dba_factory($db_host, $db_port, $db_user, $db_pass, $db_data); unset($db_host, $db_port, $db_user, $db_pass, $db_data); diff --git a/include/config.php b/include/config.php index a77801717..3292059d1 100644 --- a/include/config.php +++ b/include/config.php @@ -248,7 +248,7 @@ function load_pconfig($uid) { * @return mixed Stored value or false if it does not exist */ function get_pconfig($uid, $family, $key, $instore = false) { - logger('include/config.php: get_pconfig() deprecated instore param used', LOGGER_DEBUG); +// logger('include/config.php: get_pconfig() deprecated instore param used', LOGGER_DEBUG); global $a; if($uid === false) diff --git a/include/conversation.php b/include/conversation.php index b0a388a68..92ba18d13 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -1341,8 +1341,10 @@ function prepare_page($item) { // the template will get passed an unobscured title. $body = prepare_body($item,true); - - return replace_macros(get_markup_template('page_display.tpl'),array( + $tpl = get_pconfig($item['uid'],'system','pagetemplate'); + if (! $tpl) + $tpl = 'page_display.tpl'; + return replace_macros(get_markup_template($tpl),array( '$author' => (($naked) ? '' : $item['author']['xchan_name']), '$auth_url' => (($naked) ? '' : zid($item['author']['xchan_url'])), '$date' => (($naked) ? '' : datetime_convert('UTC',date_default_timezone_get(),$item['created'],'Y-m-d H:i')), diff --git a/include/diaspora.php b/include/diaspora.php index f9fd3b4ee..f0687e51e 100755 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -206,10 +206,7 @@ function diaspora_process_outbound($arr) { if(! $contact['xchan_pubkey']) continue; - if(activity_match($target_item['verb'],ACTIVITY_DISLIKE)) { - continue; - } - elseif(($target_item['item_restrict'] & ITEM_DELETED) + if(($target_item['item_restrict'] & ITEM_DELETED) && (($target_item['mid'] === $target_item['parent_mid']) || $arr['followup'])) { // send both top-level retractions and relayable retractions for owner to relay diaspora_send_retraction($target_item,$arr['channel'],$contact); @@ -238,11 +235,7 @@ function diaspora_process_outbound($arr) { $contact = $arr['hub']; - if($target_item['verb'] === ACTIVITY_DISLIKE) { - // unsupported - return; - } - elseif(($target_item['deleted']) + if(($target_item['deleted']) && ($target_item['mid'] === $target_item['parent_mod'])) { // top-level retraction logger('delivery: diaspora retract: ' . $loc); @@ -256,7 +249,6 @@ function diaspora_process_outbound($arr) { return; } elseif($arr['top_level_post']) { - // currently no workable solution for sending walltowall logger('delivery: diaspora status: ' . $loc); diaspora_send_status($target_item,$arr['channel'],$contact,true); return; @@ -910,6 +902,44 @@ function diaspora_post($importer,$xml,$msg) { } + +function get_diaspora_reshare_xml($url,$recurse = 0) { + + $x = z_fetch_url($url); + if(! $x['success']) + $x = z_fetch_url(str_replace('https://','http://',$url)); + if(! $x['success']) { + logger('get_diaspora_reshare_xml: unable to fetch source url ' . $url); + return; + } + logger('get_diaspora_reshare_xml: source: ' . $x['body'], LOGGER_DEBUG); + + $source_xml = parse_xml_string($x['body'],false); + + if(! $source_xml) { + logger('get_diaspora_reshare_xml: unparseable result from ' . $url); + return ''; + } + + if($source_xml->post->status_message) { + return $source_xml; + } + + // see if it's a reshare of a reshare + + if($source_xml->root_diaspora_id && $source_xml->root_guid && $recurse < 15) { + $orig_author = notags(unxmlify($source_xml->root_diaspora_id)); + $orig_guid = notags(unxmlify($source_xml->root_guid)); + $source_url = 'https://' . substr($orig_author,strpos($orig_author,'@')+1) . '/p/' . $orig_guid . '.xml'; + $y = get_diaspora_reshare_xml($source_url,$recurse+1); + if($y) + return $y; + } + return false; +} + + + function diaspora_reshare($importer,$xml,$msg) { logger('diaspora_reshare: init: ' . print_r($xml,true), LOGGER_DATA); @@ -948,20 +978,16 @@ function diaspora_reshare($importer,$xml,$msg) { $source_url = 'https://' . substr($orig_author,strpos($orig_author,'@')+1) . '/p/' . $orig_guid . '.xml'; $orig_url = 'https://'.substr($orig_author,strpos($orig_author,'@')+1).'/posts/'.$orig_guid; - $x = z_fetch_url($source_url); - if(! $x['success']) - $x = z_fetch_url(str_replace('https://','http://',$source_url)); - if(! $x['success']) { - logger('diaspora_reshare: unable to fetch source url ' . $source_url); - return; - } - logger('diaspora_reshare: source: ' . $x['body'], LOGGER_DATA); - $source_xml = parse_xml_string($x['body'],false); + $source_xml = get_diaspora_reshare_xml($source_url); if($source_xml->post->status_message) { $body = diaspora2bb($source_xml->post->status_message->raw_message); + $orig_author = notags(unxmlify($source_xml->post->status_message->diaspora_handle)); + $orig_guid = notags(unxmlify($source_xml->post->status_message->guid)); + + // Checking for embedded pictures if($source_xml->post->status_message->photo->remote_photo_path && $source_xml->post->status_message->photo->remote_photo_name) { @@ -1003,9 +1029,9 @@ function diaspora_reshare($importer,$xml,$msg) { . "' profile='" . $orig_author_link . "' avatar='" . $orig_author_photo . "' link='" . $orig_url - . "' posted='" . datetime_convert('UTC','UTC',unxmlify($sourcexml->post->status_message->created_at)) + . "' posted='" . datetime_convert('UTC','UTC',unxmlify($source_xml->post->status_message->created_at)) . "' message_id='" . unxmlify($source_xml->post->status_message->guid) - . "]" . $body . "[/share]"; + . "']" . $body . "[/share]"; $created = unxmlify($xml->created_at); @@ -2401,7 +2427,7 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr_parent']) { + if(($item['verb'] === ACTIVITY_LIKE || $item['verb'] === ACTIVITY_DISLIKE) && $item['thr_parent']) { $p = q("select mid, parent_mid from item where mid = '%s' limit 1", dbesc($item['thr_parent']) ); @@ -2420,10 +2446,10 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { else return; - if($item['verb'] === ACTIVITY_LIKE) { + if(($item['verb'] === ACTIVITY_LIKE) && ($parent['mid'] === $parent['parent_mid'])) { $tpl = get_markup_template('diaspora_like.tpl'); $like = true; - $target_type = ( $parent['mid'] === $parent['parent_mid'] ? 'Post' : 'Comment'); + $target_type = 'Post'; $positive = 'true'; if(($item_['item_restrict'] & ITEM_DELETED)) @@ -2604,6 +2630,9 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { $parentauthorsig = base64_encode(rsa_sign($sender_signed_text,$owner['channel_prvkey'],'sha256')); + if(! $text) + logger('diaspora_send_relay: no text'); + $msg = replace_macros($tpl,array( '$guid' => xmlify($item['mid']), '$parent_guid' => xmlify($parent['mid']), diff --git a/include/follow.php b/include/follow.php index a967386e9..c8bd3c500 100644 --- a/include/follow.php +++ b/include/follow.php @@ -84,7 +84,7 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) // Premium channel, set confirm before callback to avoid recursion - if(array_key_exists('connect_url',$j) && (! $confirm)) + if(array_key_exists('connect_url',$j) && ($interactive) && (! $confirm)) goaway(zid($j['connect_url'])); diff --git a/include/hubloc.php b/include/hubloc.php index cdc9de4af..fded434d2 100644 --- a/include/hubloc.php +++ b/include/hubloc.php @@ -105,9 +105,9 @@ function remove_obsolete_hublocs() { dbesc($rr['hubloc_hash']) ); if($x) { -// proc_run('php','include/notifier.php','location',$x[0]['channel_id']); -// if($interval) -// @time_sleep_until(microtime(true) + (float) $interval); + proc_run('php','include/notifier.php','location',$x[0]['channel_id']); + if($interval) + @time_sleep_until(microtime(true) + (float) $interval); } } } diff --git a/include/identity.php b/include/identity.php index bfdd02682..fafb97bbb 100644 --- a/include/identity.php +++ b/include/identity.php @@ -27,10 +27,13 @@ function identity_check_service_class($account_id) { intval(PAGE_REMOVED) ); if(! ($r && count($r))) { + $ret['total_identities'] = 0; $ret['message'] = t('Unable to obtain identity information from database'); return $ret; } + $ret['total_identities'] = intval($r[0]['total']); + if(! service_class_allows($account_id,'total_identities',$r[0]['total'])) { $result['message'] .= upgrade_message(); return $result; @@ -166,10 +169,12 @@ function create_identity($arr) { $ret['message'] = t('No account identifier'); return $ret; } - $ret=identity_check_service_class($arr['account_id']); + $ret = identity_check_service_class($arr['account_id']); if (!$ret['success']) { return $ret; } + // save this for auto_friending + $total_identities = $ret['total_identities']; $nick = mb_strtolower(trim($arr['nickname'])); if(! $nick) { @@ -249,8 +254,8 @@ function create_identity($arr) { $r = q("insert into channel ( channel_account_id, channel_primary, channel_name, channel_address, channel_guid, channel_guid_sig, - channel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_expire_days $perms_keys ) - values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d $perms_vals ) ", + channel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_expire_days, channel_timezone $perms_keys ) + values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s' $perms_vals ) ", intval($arr['account_id']), intval($primary), @@ -262,7 +267,8 @@ function create_identity($arr) { dbesc($key['prvkey']), dbesc($key['pubkey']), intval($pageflags), - intval($expire) + intval($expire), + dbesc($a->timezone) ); @@ -389,6 +395,20 @@ function create_identity($arr) { } } + // auto-follow any of the hub's pre-configured channel choices. + // Only do this if it's the first channel for this account; + // otherwise it could get annoying. Don't make this list too big + // or it will impact registration time. + + $accts = get_config('system','auto_follow'); + if(($accts) && (! $total_identities)) { + if(! is_array($accts)) + $accts = array($accts); + foreach($accts as $acct) { + if(trim($acct)) + new_contact($newuid,trim($acct),$ret['channel'],false); + } + } call_hooks('register_account', $newuid); @@ -518,6 +538,17 @@ function identity_basic_export($channel_id, $items = false) { $ret['photo'] = array('type' => $r[0]['type'], 'data' => base64url_encode($r[0]['data'])); } + + // All other term types will be included in items, if requested. + + $r = q("select * from term where type in (%d,%d) and uid = %d", + intval(TERM_SAVEDSEARCH), + intval(TERM_THING), + intval($channel_id) + ); + if($r) + $ret['term'] = $r; + $r = q("select * from obj where obj_channel = %d", intval($channel_id) ); @@ -1338,7 +1369,7 @@ function get_default_profile_photo($size = 175) { $scheme = get_config('system','default_profile_photo'); if(! $scheme) $scheme = 'rainbow_man'; - return 'images/default_profile_photos/' . $scheme . '/' . $size . '.jpg'; + return 'images/default_profile_photos/' . $scheme . '/' . $size . '.png'; } diff --git a/include/items.php b/include/items.php index 31364de60..c9810bb7c 100755 --- a/include/items.php +++ b/include/items.php @@ -759,6 +759,8 @@ function get_item_elements($x) { $arr = array(); $arr['body'] = (($x['body']) ? htmlspecialchars($x['body'],ENT_COMPAT,'UTF-8',false) : ''); + $key = get_config('system','pubkey'); + $maxlen = get_max_import_size(); if($maxlen && mb_strlen($arr['body']) > $maxlen) { @@ -813,7 +815,7 @@ function get_item_elements($x) { $arr['sig'] = (($x['signature']) ? htmlspecialchars($x['signature'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['diaspora_meta'] = (($x['diaspora_meta']) ? $x['diaspora_meta'] : ''); + $arr['diaspora_meta'] = (($x['diaspora_signature']) ? json_encode(crypto_encapsulate($x['diaspora_signature'],$key)) : ''); $arr['object'] = activity_sanitise($x['object']); $arr['target'] = activity_sanitise($x['target']); @@ -865,7 +867,6 @@ function get_item_elements($x) { // We have to do that here because we need to cleanse the input and prevent bad stuff from getting in, // and we need plaintext to do that. - $key = get_config('system','pubkey'); if(intval($arr['item_private'])) { @@ -891,7 +892,6 @@ function get_item_elements($x) { $arr['resource_type'] = $x['resource_type']; $arr['item_restrict'] = $x['item_restrict']; $arr['item_flags'] = $x['item_flags']; - $arr['diaspora_meta'] = (($x['diaspora_meta']) ? json_encode(crypto_encapsulate($x['diaspora_meta'],$key)) : ''); $arr['attach'] = $x['attach']; } @@ -1078,7 +1078,6 @@ function encode_item($item,$mirror = false) { $x['resource_type'] = $item['resource_type']; $x['item_restrict'] = $item['item_restrict']; $x['item_flags'] = $item['item_flags']; - $x['diaspora_meta'] = crypto_unencapsulate(json_decode($item['diaspora_meta'],true),$key); $x['attach'] = $item['attach']; } @@ -1127,6 +1126,9 @@ function encode_item($item,$mirror = false) { if($item['term']) $x['tags'] = encode_item_terms($item['term']); + if($item['diaspora_meta']) + $x['diaspora_signature'] = crypto_unencapsulate(json_decode($item['diaspora_meta'],true),$key); + logger('encode_item: ' . print_r($x,true), LOGGER_DATA); return $x; diff --git a/include/message.php b/include/message.php index 88cfb7ba2..b063530d6 100644 --- a/include/message.php +++ b/include/message.php @@ -50,7 +50,7 @@ function send_message($uid = 0, $recipient='', $body='', $subject='', $replyto=' // look for any existing conversation structure if(strlen($replyto)) { - $r = q("select convid from mail where uid = %d and ( mid = '%s' or parent_mid = '%s' ) limit 1", + $r = q("select convid from mail where channel_id = %d and ( mid = '%s' or parent_mid = '%s' ) limit 1", intval(local_user()), dbesc($replyto), dbesc($replyto) diff --git a/include/nav.php b/include/nav.php index 98d1b644e..7c9abce49 100644 --- a/include/nav.php +++ b/include/nav.php @@ -185,7 +185,10 @@ EOT; if(local_user()) { - $nav['network'] = array('network', t('Matrix'), "", t('Your matrix')); + $network_options = get_pconfig(local_user(),'system','network_page_default'); + + $nav['network'] = array('network' . (($network_options) ? '?f=&' . $network_options : ''), + t('Matrix'), "", t('Your matrix')); $nav['network']['mark'] = array('', t('Mark all matrix notifications seen'), '',''); $nav['home'] = array('channel/' . $channel['channel_address'], t('Channel Home'), "", t('Channel home')); @@ -239,6 +242,12 @@ EOT; $x = array('nav' => $nav, 'usermenu' => $userinfo ); call_hooks('nav', $x); +// Not sure the best place to put this on the page. So I'm implementing it but leaving it +// turned off until somebody discovers this and figures out a good location for it. +$powered_by = ''; + +// $powered_by = '<strong>red<img class="smiley" src="' . $a->get_baseurl() . '/images/rm-16.png" alt="r#" />matrix</strong>'; + $tpl = get_markup_template('nav.tpl'); $a->page['nav'] .= replace_macros($tpl, array( @@ -250,6 +259,7 @@ EOT; '$userinfo' => $x['usermenu'], '$localuser' => local_user(), '$sel' => $a->nav_sel, + '$powered_by' => $powered_by, '$pleasewait' => t('Please wait...') )); diff --git a/include/permissions.php b/include/permissions.php index 438b807d0..61ac8aea3 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -722,6 +722,11 @@ function get_role_perms($role) { } + $x = get_config('system','role_perms'); + // let system settings over-ride any or all + if($x && is_array($x) && array_key_exists($role,$x)) + $ret = array_merge($ret,$x[$role]); + call_hooks('get_role_perms',$ret); return $ret; diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index daf1bfc25..508d82957 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -3,11 +3,24 @@ function photo_factory($data, $type = null) { $ph = null; - if(class_exists('Imagick')) { - require_once('include/photo/photo_imagick.php'); - $ph = new photo_imagick($data,$type); + $ignore_imagick = get_config('system', 'ignore_imagick'); + + if(class_exists('Imagick') && !$ignore_imagick) { + $v = Imagick::getVersion(); + preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m); + if(version_compare($m[1],'6.6.7') >= 0) { + require_once('include/photo/photo_imagick.php'); + $ph = new photo_imagick($data,$type); + } + else { + // earlier imagick versions have issues with scaling png's + // don't log this because it will just fill the logfile. + // leave this note here so those who are looking for why + // we aren't using imagick can find it + } } - else { + + if(! $ph) { require_once('include/photo/photo_gd.php'); $ph = new photo_gd($data,$type); } @@ -480,11 +493,11 @@ abstract class photo_driver { * Guess image mimetype from filename or from Content-Type header * * @arg $filename string Image filename - * @arg $fromcurl boolean Check Content-Type header from curl request + * @arg $headers string Headers to check for Content-Type (from curl request) */ function guess_image_type($filename, $headers = '') { - logger('Photo: guess_image_type: '.$filename . ($fromcurl?' from curl headers':''), LOGGER_DEBUG); + logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG); $type = null; if ($headers) { $a = get_app(); @@ -494,21 +507,35 @@ function guess_image_type($filename, $headers = '') { list($k,$v) = array_map("trim", explode(":", trim($l), 2)); $hdrs[$k] = $v; } + logger('Curl headers: '.var_export($hdrs, true), LOGGER_DEBUG); if (array_key_exists('Content-Type', $hdrs)) $type = $hdrs['Content-Type']; } if (is_null($type)){ -// FIXME!!!! + + $ignore_imagick = get_config('system', 'ignore_imagick'); // Guessing from extension? Isn't that... dangerous? - if(class_exists('Imagick') && file_exists($filename) && is_readable($filename)) { - /** - * Well, this not much better, - * but at least it comes from the data inside the image, - * we won't be tricked by a manipulated extension - */ - $image = new Imagick($filename); - $type = $image->getImageMimeType(); - } else { + if(class_exists('Imagick') && file_exists($filename) && is_readable($filename) && !$ignore_imagick) { + $v = Imagick::getVersion(); + preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m); + if(version_compare($m[1],'6.6.7') >= 0) { + /** + * Well, this not much better, + * but at least it comes from the data inside the image, + * we won't be tricked by a manipulated extension + */ + $image = new Imagick($filename); + $type = $image->getImageMimeType(); + } + else { + // earlier imagick versions have issues with scaling png's + // don't log this because it will just fill the logfile. + // leave this note here so those who are looking for why + // we aren't using imagick can find it + } + } + + if(is_null($type)) { $ext = pathinfo($filename, PATHINFO_EXTENSION); $ph = photo_factory(''); $types = $ph->supportedTypes(); @@ -552,7 +579,7 @@ function import_profile_photo($photo,$xchan,$thing = false) { if($photo) { $filename = basename($photo); - $type = guess_image_type($photo,true); + $type = guess_image_type($photo); if(! $type) $type = 'image/jpeg'; @@ -623,7 +650,7 @@ function import_profile_photo($photo,$xchan,$thing = false) { $photo = $a->get_baseurl() . '/' . get_default_profile_photo(); $thumb = $a->get_baseurl() . '/' . get_default_profile_photo(80); $micro = $a->get_baseurl() . '/' . get_default_profile_photo(48); - $type = 'image/jpeg'; + $type = 'image/png'; } return(array($photo,$thumb,$micro,$type,$photo_failure)); diff --git a/include/plugin.php b/include/plugin.php index c2e08a989..4f9ab71da 100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -552,6 +552,7 @@ function theme_include($file, $root = '') { $paths = array( "{$root}view/theme/$theme/$ext/$file", "{$root}view/theme/$parent/$ext/$file", + "{$root}view/site/$ext/$file", "{$root}view/$ext/$file", ); diff --git a/include/poller.php b/include/poller.php index f689059b9..2febaeb32 100644 --- a/include/poller.php +++ b/include/poller.php @@ -149,6 +149,13 @@ function poller_run($argv, $argc){ update_birthdays(); + //update statistics in config + require_once('include/statistics_fns.php'); + update_channels_total_stat(); + update_channels_active_halfyear_stat(); + update_channels_active_monthly_stat(); + update_local_posts_stat(); + // expire any read notifications over a month old q("delete from notify where seen = 1 and date < UTC_TIMESTAMP() - INTERVAL 30 DAY"); diff --git a/include/reddav.php b/include/reddav.php index c4ef5bd08..5c93daf1f 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -9,22 +9,30 @@ * You find the original SabreDAV classes under @ref vendor/sabre/dav/. * We need to use SabreDAV 1.8.x for PHP5.3 compatibility. SabreDAV >= 2.0 * requires PHP >= 5.4. + * + * @todo split up the classes into own files. */ use Sabre\DAV; + require_once('vendor/autoload.php'); require_once('include/attach.php'); - /** * @brief RedDirectory class. * * A class that represents a directory. + * + * @extends \Sabre\DAV\Node + * @implements \Sabre\DAV\ICollection + * @implements \Sabre\DAV\IQuota */ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { /** * @brief The path inside /cloud + * + * @var string */ private $red_path; private $folder_hash; @@ -32,6 +40,7 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { * @brief The full path as seen in the browser. * /cloud + $red_path * @todo I think this is not used anywhere, we always strip '/cloud' and only use it in debug + * @var string */ private $ext_path; private $root_dir = ''; @@ -39,6 +48,8 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { /** * @brief The real path on the filesystem. * The actual path in store/ with the hashed names. + * + * @var string */ private $os_path = ''; @@ -107,7 +118,7 @@ class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { if (get_config('system', 'block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { throw new DAV\Exception\Forbidden('Permission denied.'); } - + if (($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id, $this->auth->observer, 'view_storage'))) { throw new DAV\Exception\Forbidden('Permission denied.'); } @@ -971,78 +982,111 @@ function RedFileData($file, &$auth, $test = false) { /** - * RedBasicAuth class. + * @brief Authentication backend class for RedDAV. + * + * This class also contains some data which is not necessary for authentication + * like timezone settings. * */ class RedBasicAuth extends DAV\Auth\Backend\AbstractBasic { - // @fixme mod/cloud.php:61 - public $channel_name = ''; - // @fixme mod/cloud.php:62 + /** + * @brief This variable holds the currently logged-in channel_address. + * + * It is used for building path in filestorage/. + * + * @var string|null + */ + protected $channel_name = null; + /** + * channel_id of the current channel of the logged-in account. + * + * @var int + */ public $channel_id = 0; - // @fixme mod/cloud.php:63 + /** + * channel_hash of the current channel of the logged-in account. + * + * @var string + */ public $channel_hash = ''; - // @fixme mod/cloud.php:68 + /** + * Set in mod/cloud.php to observer_hash. + * + * @var string + */ public $observer = ''; - // @fixme include/reddav.php:51 + /** + * + * @see RedBrowser::set_writeable() + * @var DAV\Browser\Plugin + */ public $browser; - // @fixme include/reddav.php:92 - public $owner_id; - // @fixme include/reddav.php:283 + /** + * channel_id of the current visited path. Set in RedDirectory::getDir(). + * + * @var int + */ + public $owner_id = 0; + /** + * channel_name of the current visited path. Set in RedDirectory::getDir(). + * + * Used for creating the path in cloud/ + * + * @var string + */ public $owner_nick = ''; - // @fixme mod/cloud.php:66 - public $timezone; + /** + * Timezone from the visiting channel's channel_timezone. + * + * Used in @ref RedBrowser + * + * @var string + */ + protected $timezone = ''; + /** + * @brief Validates a username and password. * + * Guest access is granted with the password "+++". + * + * @see DAV\Auth\Backend\AbstractBasic::validateUserPass * @param string $username * @param string $password + * @return bool */ protected function validateUserPass($username, $password) { - if (trim($password) === '+++') { - logger('reddav: validateUserPass: guest ' . $username); + logger('(DAV): RedBasicAuth::validateUserPass(): 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", + $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) { - $this->currentUser = $r[0]['channel_address']; - $this->channel_name = $r[0]['channel_address']; - $this->channel_id = $r[0]['channel_id']; - $this->channel_hash = $this->observer = $r[0]['channel_hash']; - $_SESSION['uid'] = $r[0]['channel_id']; - $_SESSION['account_id'] = $r[0]['channel_account_id']; - $_SESSION['authenticated'] = true; - return true; + return $this->setAuthenticated($r[0]); } } - $r = q("select * from channel where channel_address = '%s' limit 1", + $r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1", dbesc($username) ); if ($r) { - $x = q("select * from account where account_id = %d limit 1", + $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('(DAV) RedBasicAuth: password verified for ' . $username); - $this->currentUser = $r[0]['channel_address']; - $this->channel_name = $r[0]['channel_address']; - $this->channel_id = $r[0]['channel_id']; - $this->channel_hash = $this->observer = $r[0]['channel_hash']; - $_SESSION['uid'] = $r[0]['channel_id']; - $_SESSION['account_id'] = $r[0]['channel_account_id']; - $_SESSION['authenticated'] = true; - return true; + return $this->setAuthenticated($r[0]); } } } @@ -1051,340 +1095,88 @@ class RedBasicAuth extends DAV\Auth\Backend\AbstractBasic { return false; } - public function setCurrentUser($name) { - $this->currentUser = $name; + /** + * @brief Sets variables and session parameters after successfull authentication. + * + * @param array $r + * Array with the values for the authenticated channel. + * @return bool + */ + protected function setAuthenticated($r) { + $this->channel_name = $r['channel_address']; + $this->channel_id = $r['channel_id']; + $this->channel_hash = $this->observer = $r['channel_hash']; + $_SESSION['uid'] = $r['channel_id']; + $_SESSION['account_id'] = $r['channel_account_id']; + $_SESSION['authenticated'] = true; + return true; } /** - * @brief Set browser plugin. + * Sets the channel_name from the currently logged-in channel. * - * @see RedBrowser::set_writeable() - * @param DAV\Browser\Plugin $browser + * @param string $name + * The channel's name */ - public function setBrowserPlugin($browser) { - $this->browser = $browser; - } - - // internal? logging function - function log() { - logger('dav: auth: channel_name ' . $this->channel_name, LOGGER_DATA); - logger('dav: auth: channel_id ' . $this->channel_id, LOGGER_DATA); - logger('dav: auth: channel_hash ' . $this->channel_hash, LOGGER_DATA); - logger('dav: auth: observer ' . $this->observer, LOGGER_DATA); - logger('dav: auth: owner_id ' . $this->owner_id, LOGGER_DATA); - logger('dav: auth: owner_nick ' . $this->owner_nick, LOGGER_DATA); - } - -} // class RedBasicAuth - - - -/** - * RedBrowser class. - * - */ -class RedBrowser extends DAV\Browser\Plugin { - - private $auth; - - function __construct(&$auth) { - $this->auth = $auth; - $this->enableAssets = false; - } - - // The DAV browser is instantiated after the auth module and directory classes but before we know the current - // directory and who the owner and observer are. So we add a pointer to the browser into the auth module and vice - // versa. Then when we've figured out what directory is actually being accessed, we call the following function - // to decide whether or not to show web elements which include writeable objects. - function set_writeable() { - if (! $this->auth->owner_id) { - $this->enablePost = false; - } - - if (! perm_is_allowed($this->auth->owner_id, get_observer_hash(), 'write_storage')) { - $this->enablePost = false; - } else { - $this->enablePost = true; - } + public function setCurrentUser($name) { + $this->channel_name = $name; } - /** - * @brief Creates the directory listing for the given path. + * Returns information about the currently logged-in channel. * - * @param string $path which should be displayed + * If nobody is currently logged in, this method should return null. + * + * @see DAV\Auth\Backend\AbstractBasic::getCurrentUser + * @return string|null */ - public function generateDirectoryIndex($path) { - // (owner_id = channel_id) is visitor owner of this directory? - $is_owner = ((local_user() && $this->auth->owner_id == local_user()) ? true : false); - - if ($this->auth->timezone) - date_default_timezone_set($this->auth->timezone); - - require_once('include/conversation.php'); - - if ($this->auth->owner_nick) { - $html = profile_tabs(get_app(), (($is_owner) ? true : false), $this->auth->owner_nick); - } - - $files = $this->server->getPropertiesForPath($path, array( - '{DAV:}displayname', - '{DAV:}resourcetype', - '{DAV:}getcontenttype', - '{DAV:}getcontentlength', - '{DAV:}getlastmodified', - ), 1); - - $parent = $this->server->tree->getNodeForPath($path); - - $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); - - $parentpath['icon'] = $this->enableAssets ? '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="' . t('parent') . '"></a>' : ''; - $parentpath['path'] = $fullPath; - } - - $f = array(); - foreach ($files as $file) { - $ft = array(); - $type = null; - - // This is the current directory, we can skip it - if (rtrim($file['href'],'/')==$path) continue; - - list(, $name) = DAV\URLUtil::splitPath($file['href']); - - if (isset($file[200]['{DAV:}resourcetype'])) { - $type = $file[200]['{DAV:}resourcetype']->getValue(); - - // resourcetype can have multiple values - if (!is_array($type)) $type = array($type); - - foreach ($type as $k=>$v) { - // Some name mapping is preferred - switch ($v) { - case '{DAV:}collection' : - $type[$k] = t('Collection'); - break; - case '{DAV:}principal' : - $type[$k] = t('Principal'); - break; - case '{urn:ietf:params:xml:ns:carddav}addressbook' : - $type[$k] = t('Addressbook'); - break; - case '{urn:ietf:params:xml:ns:caldav}calendar' : - $type[$k] = t('Calendar'); - break; - case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : - $type[$k] = t('Schedule Inbox'); - break; - case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : - $type[$k] = t('Schedule Outbox'); - break; - case '{http://calendarserver.org/ns/}calendar-proxy-read' : - $type[$k] = 'Proxy-Read'; - break; - case '{http://calendarserver.org/ns/}calendar-proxy-write' : - $type[$k] = 'Proxy-Write'; - break; - } - } - $type = implode(', ', $type); - } - - // If no resourcetype was found, we attempt to use - // the contenttype property - if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { - $type = $file[200]['{DAV:}getcontenttype']; - } - if (!$type) $type = t('Unknown'); - - $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, '/')); - - $displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name; - - $displayName = $this->escapeHTML($displayName); - $type = $this->escapeHTML($type); - - $icon = ''; - if ($this->enableAssets) { - $node = $this->server->tree->getNodeForPath(($path ? $path . '/' : '') . $name); - foreach (array_reverse($this->iconMap) as $class=>$iconName) { - if ($node instanceof $class) { - $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24"></a>'; - break; - } - } - } - - $parentHash = ""; - $owner = $this->auth->owner_id; - $splitPath = split("/", $fullPath); - if (count($splitPath) > 3) { - for ($i = 3; $i < count($splitPath); $i++) { - $attachName = urldecode($splitPath[$i]); - $attachHash = $this->findAttachHash($owner, $parentHash, $attachName); - $parentHash = $attachHash; - } - } - - $attachIcon = ""; // "<a href=\"attach/".$attachHash."\" title=\"".$displayName."\"><i class=\"icon-download\"></i></a>"; - - // put the array for this file together - $ft['attachId'] = $this->findAttachIdByHash($attachHash); - $ft['fileStorageUrl'] = substr($fullPath, 0, strpos($fullPath, "cloud/")) . "filestorage/" . $this->auth->channel_name; - $ft['icon'] = $icon; - $ft['attachIcon'] = (($size) ? $attachIcon : ''); - // @todo Should this be an item value, not a global one? - $ft['is_owner'] = $is_owner; - $ft['fullPath'] = $fullPath; - $ft['displayName'] = $displayName; - $ft['type'] = $type; - $ft['size'] = $size; - $ft['sizeFormatted'] = $this->userReadableSize($size); - $ft['lastmodified'] = (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(), $lastmodified) : ''); - - $f[] = $ft; - } - - // Storage and quota for the account (all channels of the owner of this directory)! - $limit = 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) - ); - $used = $r[0]['total']; - if ($used) { - $quotaDesc = t('%1$s used'); - $quotaDesc = sprintf($quotaDesc, - $this->userReadableSize($used)); - } - if ($limit && $used) { - $quotaDesc = t('%1$s used of %2$s (%3$s%)'); - $quotaDesc = sprintf($quotaDesc, - $this->userReadableSize($used), - $this->userReadableSize($limit), - round($used / $limit, 1)); - } - - // prepare quota for template - $quota['used'] = $used; - $quota['limit'] = $limit; - $quota['desc'] = $quotaDesc; - - $html .= replace_macros(get_markup_template('cloud_directory.tpl'), array( - '$header' => t('Files') . ": " . $this->escapeHTML($path) . "/", - '$parentpath' => $parentpath, - '$entries' => $f, - '$quota' => $quota, - '$name' => t('Name'), - '$type' => t('Type'), - '$size' => t('Size'), - '$lastmod' => t('Last Modified'), - '$parent' => t('parent'), - '$edit' => t('Edit'), - '$delete' => t('Delete'), - '$total' => t('Total') - )); - - $output = ''; - if ($this->enablePost) { - $this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output)); - } - $html .= $output; - - get_app()->page['content'] = $html; - construct_page(get_app()); - } - - function userReadableSize($size) { - $ret = ""; - if (is_numeric($size)) { - $incr = 0; - $k = 1024; - $unit = array('bytes', 'KB', 'MB', 'GB', 'TB', 'PB'); - while (($size / $k) >= 1){ - $incr++; - $size = round($size / $k, 2); - } - $ret = $size . " " . $unit[$incr]; - } - return $ret; + public function getCurrentUser() { + return $this->channel_name; } /** - * Creates a form to add new folders and upload files. + * @brief Sets the timezone from the channel in RedBasicAuth. * - * @param DAV\INode $node - * @param string &$output + * Set in mod/cloud.php if the channel has a timezone set. + * + * @param string $timezone + * The channel's timezone. + * @return void */ - public function htmlActionsPanel(DAV\INode $node, &$output) { - - //Removed link to filestorage page - //if($this->auth->owner_id && $this->auth->owner_id == $this->auth->channel_id) { - // $channel = get_app()->get_channel(); - // if($channel) { - // $output .= '<tr><td colspan="2"><a href="filestorage/' . $channel['channel_address'] . '" >' . t('Edit File properties') . '</a></td></tr><tr><td> </td></tr>'; - // } - //} - - if (! $node instanceof DAV\ICollection) - return; - - // We also know fairly certain that if an object is a non-extended - // SimpleCollection, we won't need to show the panel either. - if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') - return; - - $output .= replace_macros(get_markup_template('cloud_actionspanel.tpl'), array( - '$folder_header' => t('Create new folder'), - '$folder_submit' => t('Create'), - '$upload_header' => t('Upload file'), - '$upload_submit' => t('Upload') - )); + public function setTimezone($timezone) { + $this->timezone = $timezone; } - /** - * This method takes a path/name of an asset and turns it into url - * suiteable for http access. + * @brief Returns the timezone. * - * @param string $assetName * @return string + * Return the channel's timezone. */ - protected function getAssetUrl($assetName) { - return z_root() . '/cloud/?sabreAction=asset&assetName=' . urlencode($assetName); + public function getTimezone() { + return $this->timezone; } - protected function findAttachHash($owner, $parentHash, $attachName) { - $r = q("SELECT * FROM attach WHERE uid = %d AND folder = '%s' AND filename = '%s' ORDER BY edited desc LIMIT 1", - intval($owner), - dbesc($parentHash), - dbesc($attachName) - ); - $hash = ""; - if ($r) { - foreach ($r as $rr) { - $hash = $rr['hash']; - } - } - return $hash; + /** + * @brief Set browser plugin for SabreDAV. + * + * @see RedBrowser::set_writeable() + * @param DAV\Browser\Plugin $browser + */ + public function setBrowserPlugin($browser) { + $this->browser = $browser; } - protected function findAttachIdByHash($attachHash) { - $r = q("SELECT * FROM attach WHERE hash = '%s' ORDER BY edited DESC LIMIT 1", - dbesc($attachHash) - ); - $id = ""; - if ($r) { - foreach ($r as $rr) { - $id = $rr['id']; - } - } - return $id; + /** + * Prints out all RedBasicAuth variables to logger(). + * + * @return void + */ + public function log() { + logger('dav: auth: channel_name ' . $this->channel_name, LOGGER_DATA); + logger('dav: auth: channel_id ' . $this->channel_id, LOGGER_DATA); + logger('dav: auth: channel_hash ' . $this->channel_hash, LOGGER_DATA); + logger('dav: auth: observer ' . $this->observer, LOGGER_DATA); + logger('dav: auth: owner_id ' . $this->owner_id, LOGGER_DATA); + logger('dav: auth: owner_nick ' . $this->owner_nick, LOGGER_DATA); } -} // class RedBrowser +} // class RedBasicAuth diff --git a/include/statistics_fns.php b/include/statistics_fns.php new file mode 100644 index 000000000..4f72e6615 --- /dev/null +++ b/include/statistics_fns.php @@ -0,0 +1,73 @@ +<?php /** @file */ + +function update_channels_total_stat() { + $r = q("select count(channel_id) as channels_total from channel left join account on account_id = channel_account_id + where account_flags = 0 "); + if($r) { + $channels_total_stat = intval($r[0]['channels_total']); + set_config('system','channels_total_stat',$channels_total_stat); + } else { + set_config('system','channels_total_stat',null); + } +} + +function update_channels_active_halfyear_stat() { + $r = q("select channel_id from channel left join account on account_id = channel_account_id + where account_flags = 0 and account_lastlog > UTC_TIMESTAMP - INTERVAL 6 MONTH"); + if($r) { + $s = ''; + foreach($r as $rr) { + if($s) + $s .= ','; + $s .= intval($rr['channel_id']); + } + $x = q("select uid from item where uid in ( $s ) and (item_flags & %d) and created > UTC_TIMESTAMP - INTERVAL 6 MONTH group by uid", + intval(ITEM_WALL) + ); + if($x) { + $channels_active_halfyear_stat = count($x); + set_config('system','channels_active_halfyear_stat',$channels_active_halfyear_stat); + } else { + set_config('system','channels_active_halfyear_stat',null); + } + } else { + set_config('system','channels_active_halfyear_stat',null); + } +} + +function update_channels_active_monthly_stat() { + $r = q("select channel_id from channel left join account on account_id = channel_account_id + where account_flags = 0 and account_lastlog > UTC_TIMESTAMP - INTERVAL 1 MONTH"); + if($r) { + $s = ''; + foreach($r as $rr) { + if($s) + $s .= ','; + $s .= intval($rr['channel_id']); + } + $x = q("select uid from item where uid in ( $s ) and ( item_flags & %d ) and created > UTC_TIMESTAMP - INTERVAL 1 MONTH group by uid", + intval(ITEM_WALL) + ); + if($x) { + $channels_active_monthly_stat = count($x); + set_config('system','channels_active_monthly_stat',$channels_active_monthly_stat); + } else { + set_config('system','channels_active_monthly_stat',null); + } + } else { + set_config('system','channels_active_monthly_stat',null); + } +} + +function update_local_posts_stat() { + $posts = q("SELECT COUNT(*) AS local_posts FROM `item` WHERE (item_flags & %d) ", + intval(ITEM_WALL) ); + if (is_array($posts)) { + $local_posts_stat = intval($posts[0]["local_posts"]); + set_config('system','local_posts_stat',$local_posts_stat); + } else { + set_config('system','local_posts_stat',null); + } +} + + diff --git a/include/text.php b/include/text.php index 22cf17866..0e38de2d2 100644 --- a/include/text.php +++ b/include/text.php @@ -815,28 +815,26 @@ function micropro($contact, $redirect = false, $class = '', $textmode = false) { function search($s,$id='search-box',$url='/search',$save = false) { $a = get_app(); - $o = '<div id="' . $id . '">'; - $o .= '<form action="' . $a->get_baseurl((stristr($url,'network')) ? true : false) . $url . '" method="get" >'; - $o .= '<input type="text" class="icon-search" name="search" id="search-text" placeholder="" value="' . $s .'" onclick="this.submit();" />'; - $o .= '<input class="search-submit btn btn-default" type="submit" name="submit" id="search-submit" value="' . t('Search') . '" />'; - if(feature_enabled(local_user(),'savedsearch')) - $o .= '<input class="search-save btn btn-default" type="submit" name="save" id="search-save" value="' . t('Save') . '" />'; - $o .= '</form></div>'; - return $o; + return replace_macros(get_markup_template('searchbox.tpl'),array( + '$s' => $s, + '$id' => $id, + '$action_url' => $a->get_baseurl((stristr($url,'network')) ? true : false) . $url, + '$search_label' => t('Search'), + '$save_label' => t('Save'), + '$savedsearch' => feature_enabled(local_user(),'savedsearch') + )); } function searchbox($s,$id='search-box',$url='/search',$save = false) { - $a = get_app(); - $o = '<div id="' . $id . '">'; - $o .= '<form action="' . z_root() . '/' . $url . '" method="get" >'; - $o .= '<input type="hidden" name="f" value="" />'; - $o .= '<input type="text" class="icon-search" name="search" id="search-text" placeholder="" value="' . $s .'" onclick="this.submit();" />'; - $o .= '<input type="submit" name="submit" class="btn btn-default" id="search-submit" value="' . t('Search') . '" />'; - if(feature_enabled(local_user(),'savedsearch')) - $o .= '<input type="submit" name="searchsave" class="btn btn-default" id="search-save" value="' . t('Save') . '" />'; - $o .= '</form></div>'; - return $o; + return replace_macros(get_markup_template('searchbox.tpl'),array( + '$s' => $s, + '$id' => $id, + '$action_url' => z_root() . '/' . $url, + '$search_label' => t('Search'), + '$save_label' => t('Save'), + '$savedsearch' => feature_enabled(local_user(),'savedsearch') + )); } @@ -2061,4 +2059,4 @@ function extra_query_args() { } } return $s; -}
\ No newline at end of file +} diff --git a/include/widgets.php b/include/widgets.php index f1c9ceada..8905df59a 100644 --- a/include/widgets.php +++ b/include/widgets.php @@ -532,6 +532,8 @@ function widget_mailmenu($arr) { $a = get_app(); return replace_macros(get_markup_template('message_side.tpl'), array( + '$title' => t('Messages'), + '$tabs'=> array(), '$check'=>array( diff --git a/include/zot.php b/include/zot.php index 828194c3a..3d59f00f3 100644 --- a/include/zot.php +++ b/include/zot.php @@ -1674,8 +1674,6 @@ function process_location_delivery($sender,$arr,$deliveries) { logger('process_location_delivery: results: ' . print_r($x,true), LOGGER_DATA); } -// We need to merge this code with that in the import_xchan function so as to make it -// easier to maintain changes. function sync_locations($sender,$arr,$absolute = false) { |