diff --git a/INSTALL b/INSTALL index f7c2d21caf..ff755f2471 100644 --- a/INSTALL +++ b/INSTALL @@ -49,6 +49,19 @@ functional setup of GNU Social: - php5-mysqlnd The native driver for PHP5 MariaDB connections. If you use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough. +Or, for PHP7, some or all of these will be necessary. PHP7 support is still +experimental and not necessarily working: + php7.0-bcmath + php7.0-curl + php7.0-exif + php7.0-gd + php7.0-intl + php7.0-mbstring + php7.0-mysqlnd + php7.0-opcache + php7.0-readline + php7.0-xmlwriter + The above package names are for Debian based systems. In the case of Arch Linux, PHP is compiled with support for most extensions but they require manual enabling in the relevant php.ini file (mostly php5-gmp). diff --git a/actions/apihelptest.php b/actions/apihelptest.php index a9cd7394c9..e57ebb710f 100644 --- a/actions/apihelptest.php +++ b/actions/apihelptest.php @@ -28,9 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Returns the string "ok" in the requested format with a 200 OK HTTP status code. @@ -44,29 +42,9 @@ if (!defined('STATUSNET')) { */ class ApiHelpTestAction extends ApiPrivateAuthAction { - /** - * Take arguments for running - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - function prepare($args) + protected function handle() { - parent::prepare($args); - return true; - } - - /** - * Handle the request - * - * @param array $args $_REQUEST data (unused) - * - * @return void - */ - function handle($args) - { - parent::handle($args); + parent::handle(); if ($this->format == 'xml') { $this->initDocument('xml'); @@ -77,12 +55,8 @@ class ApiHelpTestAction extends ApiPrivateAuthAction print '"ok"'; $this->endDocument('json'); } else { - $this->clientError( - // TRANS: Client error displayed when coming across a non-supported API method. - _('API method not found.'), - 404, - $this->format - ); + // TRANS: Client error displayed when coming across a non-supported API method. + throw new ClientException(_('API method not found.'), 404); } } diff --git a/classes/File.php b/classes/File.php index 9643b78f18..46d4d5498d 100644 --- a/classes/File.php +++ b/classes/File.php @@ -516,6 +516,11 @@ class File extends Managed_DataObject return $filepath; } + public function getAttachmentUrl() + { + return common_local_url('attachment', array('attachment'=>$this->getID())); + } + public function getUrl($prefer_local=true) { if ($prefer_local && !empty($this->filename)) { diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 8a490fb18a..d1b266c90b 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -173,12 +173,11 @@ class File_redirection extends Managed_DataObject try { $r = File_redirection::getByUrl($in_url); - $f = File::getKV('id',$r->file_id); - - if($file instanceof File) { + try { + $f = File::getByID($r->file_id); $r->file = $f; - $r->redir_url = $f->url; - } else { + $r->redir_url = $f->url; + } catch (NoResultException $e) { // Invalid entry, delete and run again common_log(LOG_ERR, "Could not find File with id=".$r->file_id." referenced in File_redirection, deleting File redirection entry and and trying again..."); $r->delete(); diff --git a/classes/Notice.php b/classes/Notice.php index b387b627bf..f38de37167 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -955,10 +955,10 @@ class Notice extends Managed_DataObject $act->context = new ActivityContext(); } - if (array_key_exists('http://activityschema.org/collection/public', $act->context->attention)) { + if (array_key_exists(ActivityContext::ATTN_PUBLIC, $act->context->attention)) { $stored->scope = Notice::PUBLIC_SCOPE; // TODO: maybe we should actually keep this? if the saveAttentions thing wants to use it... - unset($act->context->attention['http://activityschema.org/collection/public']); + unset($act->context->attention[ActivityContext::ATTN_PUBLIC]); } else { $stored->scope = self::figureOutScope($actor, $groups, $scope); } @@ -1537,12 +1537,16 @@ class Notice extends Managed_DataObject function getProfileTags() { - $profile = $this->getProfile(); - $list = $profile->getOtherTags($profile); $ptags = array(); + try { + $profile = $this->getProfile(); + $list = $profile->getOtherTags($profile); - while($list->fetch()) { - $ptags[] = clone($list); + while($list->fetch()) { + $ptags[] = clone($list); + } + } catch (Exception $e) { + common_log(LOG_ERR, "Error during Notice->getProfileTags() for id=={$this->getID()}: {$e->getMessage()}"); } return $ptags; @@ -3087,6 +3091,79 @@ class Notice extends Managed_DataObject $schema = Schema::get(); $schemadef = $schema->getTableDef($table); + /** + * Make sure constraints are met before upgrading, if foreign keys + * are not already in use. + * 2016-03-31 + */ + if (!isset($schemadef['foreign keys'])) { + $newschemadef = self::schemaDef(); + printfnq("\nConstraint checking Notice table...\n"); + /** + * Improve typing and make sure no NULL values in any id-related columns are 0 + * 2016-03-31 + */ + foreach (['reply_to', 'repeat_of'] as $field) { + $notice = new Notice(); // reset the object + $notice->query(sprintf('UPDATE %1$s SET %2$s=NULL WHERE %2$s=0', $notice->escapedTableName(), $field)); + // Now we're sure that no Notice entries have repeat_of=0, only an id > 0 or NULL + unset($notice); + } + + /** + * This Will find foreign keys which do not fulfill the constraints and fix + * where appropriate, such as delete when "repeat_of" ID not found in notice.id + * or set to NULL for "reply_to" in the same case. + * 2016-03-31 + * + * XXX: How does this work if we would use multicolumn foreign keys? + */ + foreach (['reply_to' => 'reset', 'repeat_of' => 'delete', 'profile_id' => 'delete'] as $field=>$action) { + $notice = new Notice(); + + $fkeyname = $notice->tableName().'_'.$field.'_fkey'; + assert(isset($newschemadef['foreign keys'][$fkeyname])); + assert($newschemadef['foreign keys'][$fkeyname]); + + $foreign_key = $newschemadef['foreign keys'][$fkeyname]; + $fkeytable = $foreign_key[0]; + assert(isset($foreign_key[1][$field])); + $fkeycol = $foreign_key[1][$field]; + + printfnq("* {$fkeyname} ({$field} => {$fkeytable}.{$fkeycol})\n"); + + // NOTE: Above we set all repeat_of to NULL if they were 0, so this really gets them all. + $notice->whereAdd(sprintf('%1$s NOT IN (SELECT %2$s FROM %3$s)', $field, $fkeycol, $fkeytable)); + if ($notice->find()) { + printfnq("\tFound {$notice->N} notices with {$field} NOT IN notice.id, {$action}ing..."); + switch ($action) { + case 'delete': // since it's a directly dependant notice for an unknown ID we don't want it in our DB + while ($notice->fetch()) { + $notice->delete(); + } + break; + case 'reset': // just set it to NULL to be compatible with our constraints, if it was related to an unknown ID + $ids = []; + foreach ($notice->fetchAll('id') as $id) { + settype($id, 'int'); + $ids[] = $id; + } + unset($notice); + $notice = new Notice(); + $notice->query(sprintf('UPDATE %1$s SET %2$s=NULL WHERE id IN (%3$s)', + $notice->escapedTableName(), + $field, + implode(',', $ids))); + break; + default: + throw new ServerException('The programmer sucks, invalid action name when fixing table.'); + } + printfnq("DONE.\n"); + } + unset($notice); + } + } + // 2015-09-04 We move Notice location data to Notice_location // First we see if we have to do this at all if (!isset($schemadef['fields']['lat']) diff --git a/lib/activityhandlerplugin.php b/lib/activityhandlerplugin.php index 8f28da85d6..c06f723a36 100644 --- a/lib/activityhandlerplugin.php +++ b/lib/activityhandlerplugin.php @@ -279,6 +279,10 @@ abstract class ActivityHandlerPlugin extends Plugin if ($this->isMyNotice($notice)) { try { $this->deleteRelated($notice); + } catch (NoProfileException $e) { + // we failed because of database lookup failure, Notice has no recognized profile as creator + // so we skip this. If we want to remove missing notices we should do a SQL constraints check + // in the affected plugin. } catch (AlreadyFulfilledException $e) { // Nothing to see here, it's obviously already gone... } diff --git a/lib/activityobject.php b/lib/activityobject.php index 87eea13727..ca6390b725 100644 --- a/lib/activityobject.php +++ b/lib/activityobject.php @@ -491,7 +491,7 @@ class ActivityObject $object->type = self::mimeTypeToObjectType($file->mimetype); $object->id = TagURI::mint(sprintf("file:%d", $file->id)); - $object->link = common_local_url('attachment', array('attachment' => $file->id)); + $object->link = $file->getAttachmentUrl(); if ($file->title) { $object->title = $file->title; diff --git a/lib/attachmentlistitem.php b/lib/attachmentlistitem.php index 6ee3c7087b..dc22c8af29 100644 --- a/lib/attachmentlistitem.php +++ b/lib/attachmentlistitem.php @@ -87,8 +87,8 @@ class AttachmentListItem extends Widget function linkAttr() { return array('class' => 'attachment', - 'href' => $this->attachment->getUrl(false), - 'id' => 'attachment-' . $this->attachment->id, + 'href' => $this->attachment->getAttachmentUrl(), + 'id' => 'attachment-' . $this->attachment->getID(), 'title' => $this->linkTitle()); } diff --git a/lib/imagefile.php b/lib/imagefile.php index a328df9852..85fc597126 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -93,7 +93,7 @@ class ImageFile $this->type = $info[2]; $this->mimetype = $info['mime']; - if ($this->type == IMAGETYPE_JPEG && function_exists('exif_read_data')) { + if ($this->type === IMAGETYPE_JPEG && function_exists('exif_read_data')) { // Orientation value to rotate thumbnails properly $exif = @exif_read_data($this->filepath); if (is_array($exif) && isset($exif['Orientation'])) { diff --git a/lib/noticelistitem.php b/lib/noticelistitem.php index a0dcf6f30c..cbff03d973 100644 --- a/lib/noticelistitem.php +++ b/lib/noticelistitem.php @@ -189,6 +189,7 @@ class NoticeListItem extends Widget function showNoticeInfo() { if (Event::handle('StartShowNoticeInfo', array($this))) { + $this->showContextLink(); $this->showNoticeLink(); $this->showNoticeSource(); $this->showNoticeLocation(); @@ -375,14 +376,10 @@ class NoticeListItem extends Widget */ function showNoticeLink() { - $this->out->elementStart('a', array('rel' => 'bookmark', - 'class' => 'timestamp', - 'href' => Conversation::getUrlFromNotice($this->notice))); $this->out->element('time', array('class' => 'dt-published', 'datetime' => common_date_iso8601($this->notice->created), 'title' => common_exact_date($this->notice->created)), common_date_string($this->notice->created)); - $this->out->elementEnd('a'); } /** @@ -568,6 +565,18 @@ class NoticeListItem extends Widget } } + /** + * Show link to conversation view. + */ + function showContextLink() + { + $this->out->element('a', array('rel' => 'bookmark', + 'class' => 'timestamp', + 'href' => Conversation::getUrlFromNotice($this->notice)), + // TRANS: A link to the conversation view of a notice, on the local server. + _('In conversation')); + } + /** * show a link to reply to the current notice * diff --git a/lib/util.php b/lib/util.php index bc4898f847..b35eff84d8 100644 --- a/lib/util.php +++ b/lib/util.php @@ -266,7 +266,8 @@ function common_logged_in() function common_local_referer() { - return parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) === common_config('site', 'server'); + return isset($_SERVER['HTTP_REFERER']) + && parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) === common_config('site', 'server'); } function common_have_session() diff --git a/plugins/DomainStatusNetwork/extlib/regDomain.inc.php b/plugins/DomainStatusNetwork/extlib/regDomain.inc.php index 389f6c4295..076c7b8714 100644 --- a/plugins/DomainStatusNetwork/extlib/regDomain.inc.php +++ b/plugins/DomainStatusNetwork/extlib/regDomain.inc.php @@ -37,7 +37,7 @@ function getRegisteredDomain($signingDomain) { global $tldTree; - $signingDomainParts = split('\.', $signingDomain); + $signingDomainParts = explode('.', $signingDomain); $result = findRegisteredDomain($signingDomainParts, $tldTree); @@ -80,4 +80,4 @@ function findRegisteredDomain($remainingSigningDomainParts, &$treeNode) { return NULL; } -?> \ No newline at end of file +?> diff --git a/plugins/Favorite/classes/Fave.php b/plugins/Favorite/classes/Fave.php index 7dad591181..c9bb7a6dd2 100644 --- a/plugins/Favorite/classes/Fave.php +++ b/plugins/Favorite/classes/Fave.php @@ -193,6 +193,13 @@ class Fave extends Managed_DataObject $act->content = sprintf(_('%1$s favorited something by %2$s: %3$s'), $actor->getNickname(), $target->getProfile()->getNickname(), $target->getRendered()); + $act->context = new ActivityContext(); + $act->context->replyToID = $target->getUri(); + try { + $act->context->replyToURL = $target->getUrl(); + } catch (InvalidUrlException $e) { + // ok, no replyToURL, i.e. the href="" in + } $act->actor = $actor->asActivityObject(); $act->target = $target->asActivityObject(); diff --git a/plugins/Linkback/lib/util.php b/plugins/Linkback/lib/util.php index cd0476ceef..b0e99cbc94 100644 --- a/plugins/Linkback/lib/util.php +++ b/plugins/Linkback/lib/util.php @@ -201,13 +201,13 @@ function linkback_hcard($mf2, $url) { } function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) { - $content = $entry['content'] ? $entry['content'][0]['html'] : - ($entry['summary'] ? $entry['sumary'][0] : $entry['name'][0]); + $content = isset($entry['content']) ? $entry['content'][0]['html'] : + (isset($entry['summary']) ? $entry['summary'][0] : $entry['name'][0]); $rendered = common_purify($content); if($notice_or_user instanceof Notice && $entry['type'] == 'mention') { - $name = $entry['name'] ? $entry['name'][0] : substr(common_strip_html($content), 0, 20).'…'; + $name = isset($entry['name']) ? $entry['name'][0] : substr(common_strip_html($content), 0, 20).'…'; $rendered = _m('linked to this from '.htmlspecialchars($name).''); } @@ -241,12 +241,16 @@ function linkback_notice($source, $notice_or_user, $entry, $author, $mf2) { } } - if($entry['published'] || $entry['updated']) { - $options['created'] = $entry['published'] ? common_sql_date($entry['published'][0]) : common_sql_date($entry['updated'][0]); + if (isset($entry['published']) || isset($entry['updated'])) { + $options['created'] = isset($entry['published']) + ? common_sql_date($entry['published'][0]) + : common_sql_date($entry['updated'][0]); } - if($entry['photo']) { + if (isset($entry['photo']) && common_valid_http_url($entry['photo'])) { $options['urls'][] = $entry['photo'][0]; + } elseif (isset($entry['photo'])) { + common_debug('Linkback got invalid HTTP URL for photo: '._ve($entry['photo'])); } foreach((array)$entry['category'] as $tag) { @@ -287,7 +291,7 @@ function linkback_profile($entry, $mf2, $response, $target) { $author = array('name' => $entry['name']); } - if(!$author['url']) { + if (!isset($author['url']) || empty($author['url'])) { $author['url'] = array($response->getEffectiveUrl()); } @@ -299,17 +303,16 @@ function linkback_profile($entry, $mf2, $response, $target) { try { $profile = Profile::fromUri($author['url'][0]); - } catch(UnknownUriException $ex) {} - - if(!($profile instanceof Profile)) { + } catch(UnknownUriException $ex) { $profile = Profile::getKV('profileurl', $author['url'][0]); } - if(!($profile instanceof Profile)) { + // XXX: Is this a good way to create the profile? + if (!$profile instanceof Profile) { $profile = new Profile(); $profile->profileurl = $author['url'][0]; $profile->fullname = $author['name'][0]; - $profile->nickname = $author['nickname'] ? $author['nickname'][0] : str_replace(' ', '', $author['name'][0]); + $profile->nickname = isset($author['nickname']) ? $author['nickname'][0] : str_replace(' ', '', $author['name'][0]); $profile->created = common_sql_now(); $profile->insert(); } diff --git a/plugins/OStatus/actions/usersalmon.php b/plugins/OStatus/actions/usersalmon.php index ea5262aa3b..fd7b12317d 100644 --- a/plugins/OStatus/actions/usersalmon.php +++ b/plugins/OStatus/actions/usersalmon.php @@ -43,7 +43,9 @@ class UsersalmonAction extends SalmonAction if (!empty($this->activity->context->replyToID)) { try { $notice = Notice::getByUri($this->activity->context->replyToID); + common_debug('Referenced Notice object found with URI: '.$notice->getUri()); } catch (NoResultException $e) { + common_debug('Referenced Notice object NOT found with URI: '.$this->activity->context->replyToID); $notice = false; } } diff --git a/plugins/Oembed/actions/oembed.php b/plugins/Oembed/actions/oembed.php index c374b8b34f..564e492e4a 100644 --- a/plugins/Oembed/actions/oembed.php +++ b/plugins/Oembed/actions/oembed.php @@ -84,26 +84,25 @@ class OembedAction extends Action $oembed['html']=$notice->getRendered(); // maybe add thumbnail - $attachments = $notice->attachments(); - if (!empty($attachments)) { - foreach ($attachments as $attachment) { - if(is_object($attachment)) { - try { - $thumb = $attachment->getThumbnail(); - } catch (ServerException $e) { - // - } - try { - $thumb_url = File_thumbnail::url($thumb->filename); - $oembed['thumbnail_url'] = $thumb_url; - break; // only first one - } catch (ClientException $e) { - // - } - } - } - } - + foreach ($notice->attachments() as $attachment) { + if (!$attachment instanceof File) { + common_debug('ATTACHMENTS array entry from notice id=='._ve($notice->getID()).' is something else than a File dataobject: '._ve($attachment)); + continue; + } + try { + $thumb = $attachment->getThumbnail(); + $thumb_url = File_thumbnail::url($thumb->filename); + $oembed['thumbnail_url'] = $thumb_url; + break; // only first one + } catch (UseFileAsThumbnailException $e) { + $oembed['thumbnail_url'] = $attachment->getUrl(); + break; // we're happy with that + } catch (ServerException $e) { + // + } catch (ClientException $e) { + // + } + } break; case 'attachment': diff --git a/plugins/Xmpp/extlib/XMPPHP/Roster.php b/plugins/Xmpp/extlib/XMPPHP/Roster.php index 2e459e2a2f..500ff6dcc1 100644 --- a/plugins/Xmpp/extlib/XMPPHP/Roster.php +++ b/plugins/Xmpp/extlib/XMPPHP/Roster.php @@ -118,12 +118,13 @@ class Roster { * @param string $status */ public function setPresence($presence, $priority, $show, $status) { - list($jid, $resource) = split("/", $presence); + $parts = explode('/', $presence); + $jid = $parts[0]; + $resource = isset($parts[1]) ? $parts[1] : ''; // apparently we can do '' as an associative array index if ($show != 'unavailable') { if (!$this->isContact($jid)) { $this->addContact($jid, 'not-in-roster'); } - $resource = $resource ? $resource : ''; $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status); } else { //Nuke unavailable resources to save memory unset($this->roster_array[$jid]['resource'][$resource]); @@ -137,7 +138,7 @@ class Roster { * @param string $jid */ public function getPresence($jid) { - $split = split("/", $jid); + $split = explode("/", $jid); $jid = $split[0]; if($this->isContact($jid)) { $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127 diff --git a/plugins/Xmpp/extlib/XMPPHP/XMLStream.php b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php index d33411ec54..5c71426200 100644 --- a/plugins/Xmpp/extlib/XMPPHP/XMLStream.php +++ b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php @@ -263,7 +263,7 @@ class XMPPHP_XMLStream { $ns_tags = array($xpath); } foreach($ns_tags as $ns_tag) { - list($l, $r) = split("}", $ns_tag); + list($l, $r) = explode("}", $ns_tag); if ($r != null) { $xpart = array(substr($l, 1), $r); } else { @@ -564,7 +564,7 @@ class XMPPHP_XMLStream { if ($searchxml !== null) { if($handler[2] === null) $handler[2] = $this; $this->log->log("Calling {$handler[1]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[2]->$handler[1]($this->xmlobj[2]); + $handler[2]->{$handler[1]}($this->xmlobj[2]); } } } @@ -578,13 +578,13 @@ class XMPPHP_XMLStream { if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) { if($handler[3] === null) $handler[3] = $this; $this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[3]->$handler[2]($this->xmlobj[2]); + $handler[3]->{$handler[2]}($this->xmlobj[2]); } } foreach($this->idhandlers as $id => $handler) { if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) { if($handler[1] === null) $handler[1] = $this; - $handler[1]->$handler[0]($this->xmlobj[2]); + $handler[1]->{$handler[0]}($this->xmlobj[2]); #id handlers are only used once unset($this->idhandlers[$id]); break; @@ -640,7 +640,7 @@ class XMPPHP_XMLStream { if($handler[2] === null) { $handler[2] = $this; } - $handler[2]->$handler[1]($payload); + $handler[2]->{$handler[1]}($payload); } } foreach($this->until as $key => $until) {