diff --git a/src/Entity/Attention.php b/src/Entity/Attention.php index 2d0d19efc9..0c12c0a963 100644 --- a/src/Entity/Attention.php +++ b/src/Entity/Attention.php @@ -1,80 +1,65 @@ . -/* - * Data class for Attentions +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social * - * @category Data + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; + +/** + * Entity for attentions + * + * @category DB * @package GNUsocial - * @copyright 2014 Free Software Foundation, Inc http://www.fsf.org + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Attention extends Managed_DataObject +class Attention { - public $__table = 'attention'; // table name - public $notice_id; // int(4) primary_key not_null - public $profile_id; // int(4) primary_key not_null - public $reason; // varchar(191) - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE BEGIN - public static function schemaDef() + // AUTOCODE END + + public static function schemaDef(): array { - return array( + return [ + 'name' => 'attention', 'description' => 'Notice attentions to profiles (that are not a mention and not result of a subscription)', - 'fields' => array( - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice_id to give attention'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile_id for feed receiver'), - 'reason' => array('type' => 'varchar', 'length' => 191, 'description' => 'Optional reason why this was brought to the attention of profile_id'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('notice_id', 'profile_id'), - 'foreign keys' => array( - 'attention_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - 'attention_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - 'indexes' => array( - 'attention_profile_id_idx' => array('profile_id'), - ), - ); - } - - public static function saveNew(Notice $notice, Profile $target, $reason=null) - { - try { - $att = Attention::getByKeys(['notice_id'=>$notice->getID(), 'profile_id'=>$target->getID()]); - throw new AlreadyFulfilledException('Attention already exists with reason: '._ve($att->reason)); - } catch (NoResultException $e) { - $att = new Attention(); - - $att->notice_id = $notice->getID(); - $att->profile_id = $target->getID(); - $att->reason = $reason; - $att->created = common_sql_now(); - $result = $att->insert(); - - if ($result === false) { - throw new Exception('Failed Attention::saveNew for notice id=='.$notice->getID().' target id=='.$target->getID().', reason=="'.$reason.'"'); - } - } - self::blow('attention:stream:%d', $target->getID()); - return $att; + 'fields' => [ + 'notice_id' => ['type' => 'int', 'not null' => true, 'description' => 'notice_id to give attention'], + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'profile_id for feed receiver'], + 'reason' => ['type' => 'varchar', 'length' => 191, 'description' => 'Optional reason why this was brought to the attention of profile_id'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['notice_id', 'profile_id'], + 'foreign keys' => [ + 'attention_notice_id_fkey' => ['notice', ['notice_id' => 'id']], + 'attention_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + 'attention_notice_id_idx' => ['notice_id'], + 'attention_profile_id_idx' => ['profile_id'], + ], + ]; } } diff --git a/src/Entity/Avatar.php b/src/Entity/Avatar.php index 5860a3f5fc..8266d8a785 100644 --- a/src/Entity/Avatar.php +++ b/src/Entity/Avatar.php @@ -1,277 +1,68 @@ . -defined('GNUSOCIAL') || die(); +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for avatar + * Entity for user's avatar + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -class Avatar extends Managed_DataObject +class Avatar { - public $__table = 'avatar'; // table name - public $profile_id; // int(4) primary_key not_null - public $original; // bool default_false - public $width; // int(4) primary_key not_null - public $height; // int(4) primary_key not_null - public $mediatype; // varchar(32) not_null - public $filename; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE BEGIN - public static function schemaDef() + // AUTOCODE END + + public static function schemaDef(): array { - return array( - 'fields' => array( - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), - 'original' => array('type' => 'bool', 'default' => false, 'description' => 'uploaded by user or generated?'), - 'width' => array('type' => 'int', 'not null' => true, 'description' => 'image width'), - 'height' => array('type' => 'int', 'not null' => true, 'description' => 'image height'), - 'mediatype' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'file type'), - 'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'local filename, if local'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('profile_id', 'width', 'height'), - 'unique keys' => array( -// 'avatar_filename_key' => array('filename'), - ), - 'foreign keys' => array( - 'avatar_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - ); - } - - // We clean up the file, too - public function delete($useWhere = false) - { - $filename = $this->filename; - if (file_exists(Avatar::path($filename))) { - @unlink(Avatar::path($filename)); - } - - return parent::delete($useWhere); - } - - /* - * Deletes all avatars (but may spare the original) from a profile. - * - * @param Profile $target The profile we're deleting avatars of. - * @param boolean $original Whether original should be removed or not. - */ - public static function deleteFromProfile(Profile $target, $original = true) - { - try { - $avatars = self::getProfileAvatars($target); - foreach ($avatars as $avatar) { - if ($avatar->original && !$original) { - continue; - } - $avatar->delete(); - } - } catch (NoAvatarException $e) { - // There are no avatars to delete, a sort of success. - } - - return true; - } - - protected static $_avatars = []; - - /* - * Get an avatar by profile. Currently can't call newSize with $height - */ - public static function byProfile(Profile $target, $width=null, $height=null) - { - $width = intval($width); - $height = !is_null($height) ? intval($height) : null; - if (is_null($height)) { - $height = $width; - } - - $size = "{$width}x{$height}"; - if (!isset(self::$_avatars[$target->id])) { - self::$_avatars[$target->id] = array(); - } elseif (isset(self::$_avatars[$target->id][$size])) { - return self::$_avatars[$target->id][$size]; - } - - $avatar = null; - if (Event::handle('StartProfileGetAvatar', array($target, $width, &$avatar))) { - $avatar = self::pkeyGet( - array( - 'profile_id' => $target->id, - 'width' => $width, - 'height' => $height, - ) - ); - Event::handle('EndProfileGetAvatar', array($target, $width, &$avatar)); - } - - if (is_null($avatar)) { - // Obviously we can't find an avatar, so let's resize the original! - $avatar = Avatar::newSize($target, $width); - } elseif (!($avatar instanceof Avatar)) { - throw new NoAvatarException($target, $avatar); - } - - self::$_avatars[$target->id]["{$avatar->width}x{$avatar->height}"] = $avatar; - return $avatar; - } - - public static function getUploaded(Profile $target) - { - $avatar = new Avatar(); - $avatar->profile_id = $target->id; - $avatar->original = true; - if (!$avatar->find(true)) { - throw new NoAvatarException($target, $avatar); - } - if (!file_exists(Avatar::path($avatar->filename))) { - // The delete call may be odd for, say, unmounted filesystems - // that cause a file to currently not exist, but actually it does... - $avatar->delete(); - throw new NoAvatarException($target, $avatar); - } - return $avatar; - } - - public static function getProfileAvatars(Profile $target) - { - $avatar = new Avatar(); - $avatar->profile_id = $target->id; - if (!$avatar->find()) { - throw new NoAvatarException($target, $avatar); - } - return $avatar->fetchAll(); - } - - /** - * Where should the avatar go for this user? - * @param int $id user id - * @param string $extension file extension - * @param int|null $size file size - * @param string|null $extra extra bit for the filename - * @return string - */ - public static function filename(int $id, string $extension, ?int $size = null, ?string $extra = null) - { - if ($size) { - return $id . '-' . $size . (($extra) ? ('-' . $extra) : '') . $extension; - } else { - return $id . '-original' . (($extra) ? ('-' . $extra) : '') . $extension; - } - } - - public static function path($filename) - { - $dir = common_config('avatar', 'dir'); - - if ($dir[strlen($dir)-1] != '/') { - $dir .= '/'; - } - - return $dir . $filename; - } - - public static function url($filename) - { - $path = common_config('avatar', 'url_base'); - - if ($path[strlen($path)-1] != '/') { - $path .= '/'; - } - - if ($path[0] != '/') { - $path = '/'.$path; - } - - $server = common_config('avatar', 'server'); - - if (empty($server)) { - $server = common_config('site', 'server'); - } - - $ssl = (common_config('avatar', 'ssl') || GNUsocial::useHTTPS()); - - $protocol = ($ssl) ? 'https' : 'http'; - - return $protocol.'://'.$server.$path.$filename; - } - - public function displayUrl() - { - return Avatar::url($this->filename); - } - - public static function urlByProfile(Profile $target, $width = null, $height = null) - { - try { - return self::byProfile($target, $width, $height)->displayUrl(); - } catch (Exception $e) { - return self::defaultImage($width); - } - } - - public static function defaultImage($size = null) - { - if (is_null($size)) { - $size = AVATAR_PROFILE_SIZE; - } - static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile', - AVATAR_STREAM_SIZE => 'stream', - AVATAR_MINI_SIZE => 'mini'); - return Theme::path('default-avatar-'.$sizenames[$size].'.png'); - } - - public static function newSize(Profile $target, $width) - { - $width = intval($width); - if ($width < 1 || $width > common_config('avatar', 'maxsize')) { - // TRANS: An error message when avatar size is unreasonable - throw new Exception(_m('Avatar size too large')); - } - // So far we only have square avatars and I don't have time to - // rewrite support for non-square ones right now ;) - $height = $width; - - $original = Avatar::getUploaded($target); - - $imagefile = new ImageFile(null, Avatar::path($original->filename)); - $filename = Avatar::filename( - $target->getID(), - image_type_to_extension($imagefile->preferredType()), - $width, - common_timestamp() - ); - $imagefile->resizeTo(Avatar::path($filename), array('width'=>$width, 'height'=>$height)); - - $scaled = clone($original); - $scaled->original = false; - $scaled->width = $width; - $scaled->height = $height; - $scaled->filename = $filename; - $scaled->created = common_sql_now(); - - if (!$scaled->insert()) { - // TRANS: An error message when unable to insert avatar data into the db - throw new Exception(_m('Could not insert new avatar data to database')); - } - - // Return the new avatar object - return $scaled; + return [ + 'name' => 'avatar', + 'fields' => [ + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'], + 'original' => ['type' => 'bool', 'default' => false, 'description' => 'uploaded by user or generated?'], + 'width' => ['type' => 'int', 'not null' => true, 'description' => 'image width'], + 'height' => ['type' => 'int', 'not null' => true, 'description' => 'image height'], + 'mediatype' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'file type'], + 'filename' => ['type' => 'varchar', 'length' => 191, 'description' => 'local filename, if local'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['profile_id', 'width', 'height'], + 'unique keys' => [ + // 'avatar_filename_key' => array('filename'), + ], + 'foreign keys' => [ + 'avatar_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + 'avatar_profile_id_idx' => ['profile_id'], + ], + ]; } } diff --git a/src/Entity/Config.php b/src/Entity/Config.php index c457da1a5a..e989b6a5bf 100644 --- a/src/Entity/Config.php +++ b/src/Entity/Config.php @@ -1,164 +1,54 @@ . - */ + * along with GNU social. If not, see . + }}} */ -if (!defined('GNUSOCIAL')) { exit(1); } +namespace App\Entity; /** - * Table Definition for config + * Entity for app configuration + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -class Config extends Managed_DataObject +class Config { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'config'; // table name - public $section; // varchar(32) primary_key not_null - public $setting; // varchar(32) primary_key not_null - public $value; // text + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() + public static function schemaDef(): array { - return array( - 'fields' => array( - 'section' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration section'), - 'setting' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration setting'), - 'value' => array('type' => 'text', 'description' => 'configuration value'), - ), - 'primary key' => array('section', 'setting'), - ); + return [ + 'name' => 'config', + 'fields' => [ + 'section' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration section'], + 'setting' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration setting'], + 'value' => ['type' => 'text', 'description' => 'configuration value'], + ], + 'primary key' => ['section', 'setting'], + ]; } - - const settingsKey = 'config:settings'; - - static function loadSettings() - { - try { - $settings = self::_getSettings(); - if (!empty($settings)) { - self::_applySettings($settings); - } - } catch (Exception $e) { - return; - } - } - - static function _getSettings() - { - $c = self::memcache(); - - if (!empty($c)) { - $settings = $c->get(Cache::key(self::settingsKey)); - if ($settings !== false) { - return $settings; - } - } - - $settings = array(); - - $config = new Config(); - - $config->find(); - - while ($config->fetch()) { - $settings[] = array($config->section, $config->setting, $config->value); - } - - $config->free(); - - if (!empty($c)) { - $c->set(Cache::key(self::settingsKey), $settings); - } - - return $settings; - } - - static function _applySettings($settings) - { - global $config; - - foreach ($settings as $s) { - list($section, $setting, $value) = $s; - $config[$section][$setting] = $value; - } - } - - function insert() - { - $result = parent::insert(); - if ($result) { - Config::_blowSettingsCache(); - } - return $result; - } - - function delete($useWhere=false) - { - $result = parent::delete($useWhere); - if ($result !== false) { - Config::_blowSettingsCache(); - } - return $result; - } - - function update($dataObject=false) - { - $result = parent::update($dataObject); - if ($result !== false) { - Config::_blowSettingsCache(); - } - return $result; - } - - static function save($section, $setting, $value) - { - $result = null; - - $config = Config::pkeyGet(array('section' => $section, - 'setting' => $setting)); - - if (!empty($config)) { - $orig = clone($config); - $config->value = $value; - $result = $config->update($orig); - } else { - $config = new Config(); - - $config->section = $section; - $config->setting = $setting; - $config->value = $value; - - $result = $config->insert(); - } - - return $result; - } - - function _blowSettingsCache() - { - $c = self::memcache(); - - if (!empty($c)) { - $c->delete(Cache::key(self::settingsKey)); - } - } -} +} \ No newline at end of file diff --git a/src/Entity/ConfirmAddress.php b/src/Entity/ConfirmAddress.php new file mode 100644 index 0000000000..26fe73df88 --- /dev/null +++ b/src/Entity/ConfirmAddress.php @@ -0,0 +1,62 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user's email confimation + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ConfirmAddress +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'confirm_address', + 'fields' => [ + 'code' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'], + 'user_id' => ['type' => 'int', 'default' => 0, 'description' => 'user who requested confirmation'], + 'address' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'address (email, xmpp, SMS, etc.)'], + 'address_extra' => ['type' => 'varchar', 'length' => 191, 'description' => 'carrier ID, for SMS'], + 'address_type' => ['type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'], + 'claimed' => ['type' => 'datetime', 'description' => 'date this was claimed for queueing'], + 'sent' => ['type' => 'datetime', 'description' => 'date this was sent for queueing'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['code'], + 'foreign keys' => [ + 'confirm_address_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Confirm_address.php b/src/Entity/Confirm_address.php deleted file mode 100644 index 2f92e0a59a..0000000000 --- a/src/Entity/Confirm_address.php +++ /dev/null @@ -1,183 +0,0 @@ -. - -/** - * Table Definition for confirm_address - */ - -defined('GNUSOCIAL') || die(); - -class Confirm_address extends Managed_DataObject -{ - public $__table = 'confirm_address'; // table name - public $code; // varchar(32) primary_key not_null - public $user_id; // int() - public $address; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $address_extra; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $address_type; // varchar(8) not_null - public $claimed; // datetime() - public $sent; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - 'fields' => array( - 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'), - 'user_id' => array('type' => 'int', 'description' => 'user who requested confirmation'), - 'address' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'address (email, xmpp, SMS, etc.)'), - 'address_extra' => array('type' => 'varchar', 'length' => 191, 'description' => 'carrier ID, for SMS'), - 'address_type' => array('type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'), - 'claimed' => array('type' => 'datetime', 'description' => 'date this was claimed for queueing'), - 'sent' => array('type' => 'datetime', 'description' => 'date this was sent for queueing'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('code'), - 'foreign keys' => array( - 'confirm_address_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - 'indexes' => array( - 'confirm_address_user_id_idx' => array('user_id'), - ), - ); - } - - public static function getByAddress($address, $addressType) - { - $ca = new Confirm_address(); - - $ca->address = $address; - $ca->address_type = $addressType; - - if (!$ca->find(true)) { - throw new NoResultException($ca); - } - - return $ca; - } - - public static function saveNew($user, $address, $addressType, $extra = null) - { - $ca = new Confirm_address(); - - if (!empty($user)) { - $ca->user_id = $user->id; - } - - $ca->address = $address; - $ca->address_type = $addressType; - $ca->address_extra = $extra; - $ca->code = common_confirmation_code(64); - - $ca->insert(); - - return $ca; - } - - public function getAddress() - { - return $this->address; - } - - public function getAddressType() - { - return $this->address_type; - } - - public function getCode() - { - return $this->code; - } - - public function getProfile() - { - return Profile::getByID($this->user_id); - } - - public function getUrl() - { - return common_local_url('confirmaddress', array('code' => $this->code)); - } - - /** - * Supply arguments in $args. Currently known args: - * headers Array with headers (only used for email) - * nickname How we great the user (defaults to nickname, but can be any string) - * sitename Name we sign the email with (defaults to sitename, but can be any string) - * url The confirmation address URL. - */ - public function sendConfirmation(array $args = []) - { - common_debug('Sending confirmation URL for user '._ve($this->user_id).' using '._ve($this->address_type)); - - $defaults = [ - 'headers' => [], - 'nickname' => $this->getProfile()->getNickname(), - 'sitename' => common_config('site', 'name'), - 'url' => $this->getUrl(), - ]; - foreach (array_keys($defaults) as $key) { - if (!isset($args[$key])) { - $args[$key] = $defaults[$key]; - } - } - - switch ($this->getAddressType()) { - case 'email': - $this->sendEmailConfirmation($args); - break; - default: - throw ServerException('Unable to handle confirm_address address type: '._ve($this->address_type)); - } - } - - public function sendEmailConfirmation(array $args = []) - { - // TRANS: Subject for address confirmation email. - $subject = _('Email address confirmation'); - - // TRANS: Body for address confirmation email. - // TRANS: %1$s is the addressed user's nickname, %2$s is the StatusNet sitename, - // TRANS: %3$s is the URL to confirm at. - $body = sprintf( - _("Hey, %1\$s.\n\n" . - "Someone just entered this email address on %2\$s.\n\n" . - "If it was you, and you want to confirm your entry, ". - "use the URL below:\n\n\t%3\$s\n\n" . - "If not, just ignore this message.\n\n". - "Thanks for your time, \n%2\$s\n"), - $args['nickname'], - $args['sitename'], - $args['url'] - ); - - require_once INSTALLDIR . '/lib/util/mail.php'; - return mail_to_user($this->getProfile()->getUser(), $subject, $body, $args['headers'], $this->getAddress()); - } - - public function delete($useWhere = false) - { - $result = parent::delete($useWhere); - - if ($result === false) { - common_log_db_error($confirm, 'DELETE', __FILE__); - // TRANS: Server error displayed when an address confirmation code deletion from the - // TRANS: database fails in the contact address confirmation action. - throw new ServerException(_('Could not delete address confirmation.')); - } - return $result; - } -} diff --git a/src/Entity/Consumer.php b/src/Entity/Consumer.php index 1489cee071..78fa7da276 100644 --- a/src/Entity/Consumer.php +++ b/src/Entity/Consumer.php @@ -1,95 +1,57 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for consumer + * Entity for OAuth consumer + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Consumer extends Managed_DataObject +class Consumer { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'consumer'; // table name - public $consumer_key; // varchar(191) primary_key not_null not 255 because utf8mb4 takes more space - public $consumer_secret; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $seed; // char(32) not_null - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() + public static function schemaDef(): array { - return array( + return [ + 'name' => 'consumer', 'description' => 'OAuth consumer record', - 'fields' => array( - 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'), - 'consumer_secret' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'secret value'), - 'seed' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'seed for new tokens by this consumer'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('consumer_key'), - ); - } - - public static function generateNew() - { - $cons = new Consumer(); - $rand = common_random_hexstr(16); - - $cons->seed = $rand; - $cons->consumer_key = md5(time() + $rand); - $cons->consumer_secret = md5(md5(time() + time() + $rand)); - $cons->created = common_sql_now(); - - return $cons; - } - - /** - * Delete a Consumer and related tokens and nonces - * - * XXX: Should this happen in an OAuthDataStore instead? - * - */ - public function delete($useWhere = false) - { - // XXX: Is there any reason NOT to do this kind of cleanup? - - $this->deleteTokens(); - $this->deleteNonces(); - - return parent::delete($useWhere); - } - - private function deleteTokens() - { - $token = new Token(); - $token->consumer_key = $this->consumer_key; - $token->delete(); - } - - private function deleteNonces() - { - $nonce = new Nonce(); - $nonce->consumer_key = $this->consumer_key; - $nonce->delete(); + 'fields' => [ + 'consumer_key' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'], + 'consumer_secret' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'secret value'], + 'seed' => ['type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'seed for new tokens by this consumer'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['consumer_key'], + ]; } } diff --git a/src/Entity/Conversation.php b/src/Entity/Conversation.php index 6929fee4e4..55bf7f349f 100644 --- a/src/Entity/Conversation.php +++ b/src/Entity/Conversation.php @@ -1,172 +1,57 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** * Data class for Conversations * * @category Data * @package GNUsocial + * * @author Zach Copley * @author Mikael Nordfeldth * @copyright 2010 StatusNet Inc. * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Conversation extends Managed_DataObject +class Conversation { - public $__table = 'conversation'; // table name - public $id; // int(4) primary_key not_null auto_increment - public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $url; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE BEGIN - public static function schemaDef() + // AUTOCODE END + + public static function schemaDef(): array { - return array( - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'), - 'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'), - 'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'Resolvable URL, preferrably remote (local can be generated on the fly)'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'conversation_uri_key' => array('uri'), - ), - ); - } - - public static function beforeSchemaUpdate() - { - $table = strtolower(get_called_class()); - $schema = Schema::get(); - $schemadef = $schema->getTableDef($table); - - // 2016-01-06 We have to make sure there is no conversation with id==0 since it will screw up auto increment resequencing - if ($schemadef['fields']['id']['auto_increment'] ?? false) { - // since we already have auto incrementing ('serial') we can continue - return; - } - - // The conversation will be recreated in upgrade.php, which will - // generate a new URI, but that's collateral damage for you. - $conv = new Conversation(); - $conv->id = 0; - if ($conv->find()) { - while ($conv->fetch()) { - // Since we have filtered on 0 this only deletes such entries - // which I have been afraid wouldn't work, but apparently does! - // (I thought it would act as null or something and find _all_ conversation entries) - $conv->delete(); - } - } - } - - /** - * Factory method for creating a new conversation. - * - * Use this for locally initiated conversations. Remote notices should - * preferrably supply their own conversation URIs in the OStatus feed. - * - * @return Conversation the new conversation DO - */ - public static function create(ActivityContext $ctx = null, $created = null) - { - // Be aware that the Notice does not have an id yet since it's not inserted! - $conv = new Conversation(); - $conv->created = $created ?: common_sql_now(); - if ($ctx instanceof ActivityContext) { - $conv->uri = $ctx->conversation; - $conv->url = $ctx->conversation_url; - } else { - $conv->uri = sprintf( - '%s%s=%s:%s=%s', - TagURI::mint(), - 'objectType', - 'thread', - 'nonce', - common_random_hexstr(8) - ); - // locally generated Conversation objects don't get static URLs stored - $conv->url = $conv->sqlValue('NULL'); - } - // This insert throws exceptions on failure - $conv->insert(); - - return $conv; - } - - public static function noticeCount($id) - { - $keypart = sprintf('conversation:notice_count:%d', $id); - - $cnt = self::cacheGet($keypart); - - if ($cnt !== false) { - return $cnt; - } - - $notice = new Notice(); - $notice->conversation = $id; - $notice->whereAddIn('verb', array(ActivityVerb::POST, ActivityUtils::resolveUri(ActivityVerb::POST, true)), $notice->columnType('verb')); - $cnt = $notice->count(); - - self::cacheSet($keypart, $cnt); - - return $cnt; - } - - public static function getUrlFromNotice(Notice $notice, $anchor = true) - { - $conv = Conversation::getByID($notice->conversation); - return $conv->getUrl($anchor ? $notice->getID() : null); - } - - public function getUri() - { - return $this->uri; - } - - public function getUrl($noticeId=null) - { - // FIXME: the URL router should take notice-id as an argument... - return common_local_url('conversation', array('id' => $this->getID())) . - ($noticeId===null ? '' : "#notice-{$noticeId}"); - } - - // FIXME: ...will 500 ever be too low? Taken from ConversationAction::MAX_NOTICES - public function getNotices(Profile $scoped=null, $offset=0, $limit=500) - { - $stream = new ConversationNoticeStream($this->getID(), $scoped); - $notices = $stream->getNotices($offset, $limit); - return $notices; - } - - public function insert() - { - $result = parent::insert(); - if ($result === false) { - common_log_db_error($this, 'INSERT', __FILE__); - throw new ServerException(_('Failed to insert Conversation into database')); - } - return $result; + return [ + 'name' => 'conversation', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'Unique identifier, (again) unrelated to notice id since 2016-01-06'], + 'uri' => ['type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the conversation'], + 'url' => ['type' => 'varchar', 'length' => 191, 'description' => 'Resolvable URL, preferrably remote (local can be generated on the fly)'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'conversation_uri_key' => ['uri'], + ], + ]; } } diff --git a/src/Entity/File.php b/src/Entity/File.php index 2dc832edd6..a5b964a383 100644 --- a/src/Entity/File.php +++ b/src/Entity/File.php @@ -1,35 +1,41 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * @category Files + * Entity for uploaded files + * + * @category DB * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. * @author Mikael Nordfeldth - * @author Miguel Dantas - * @copyright 2008-2009, 2019 Free Software Foundation http://fsf.org + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for file - */ -class File extends Managed_DataObject +class File { +<<<<<<< HEAD public $__table = 'file'; // table name public $id; // int(4) primary_key not_null public $urlhash; // varchar(64) unique_key @@ -882,132 +888,38 @@ class File extends Managed_DataObject 'File_redirection', 'File_thumbnail', 'File_to_post' +======= + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'file', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true], + 'urlhash' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 of destination URL (url field)'], + 'url' => ['type' => 'text', 'description' => 'destination URL after following possible redirections'], + 'filehash' => ['type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'], + 'mimetype' => ['type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'], + 'size' => ['type' => 'int', 'description' => 'size of resource when available'], + 'title' => ['type' => 'text', 'description' => 'title of resource when available'], + 'date' => ['type' => 'int', 'description' => 'date of resource according to http query'], + 'protected' => ['type' => 'int', 'description' => 'true when URL is private (needs login)'], + 'filename' => ['type' => 'text', 'description' => 'if file is stored locally (too) this is the filename'], + 'width' => ['type' => 'int', 'description' => 'width in pixels, if it can be described as such and data is available'], + 'height' => ['type' => 'int', 'description' => 'height in pixels, if it can be described as such and data is available'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'file_urlhash_key' => ['urlhash'], + ], + 'indexes' => [ + 'file_filehash_idx' => ['filehash'], + ], +>>>>>>> ecc5139ce5 ([DATABASE] Extracted schemaDef method from old files and refactored onto new files) ]; - Event::handle('FileDeleteRelated', [$this, &$related]); - - foreach ($related as $cls) { - $inst = new $cls(); - $inst->file_id = $this->id; - if ($inst->find()) { - while ($inst->fetch()) { - $inst->delete(); - } - } - } - - // And finally remove the entry from the database - return parent::delete($useWhere); - } - - public function getTitle() - { - $title = $this->title ?: MediaFile::getDisplayName($this); - - return $title ?: null; - } - - public function setTitle($title) - { - $orig = clone($this); - $this->title = mb_strlen($title) > 0 ? $title : null; - return $this->update($orig); - } - - public static function hashurl($url) - { - if (empty($url)) { - throw new Exception('No URL provided to hash algorithm.'); - } - return hash(self::URLHASH_ALG, $url); - } - - public static function beforeSchemaUpdate() - { - $table = strtolower(get_called_class()); - $schema = Schema::get(); - $schemadef = $schema->getTableDef($table); - - // 2015-02-19 We have to upgrade our table definitions to have the urlhash field populated - if (isset($schemadef['fields']['urlhash']) && isset($schemadef['unique keys']['file_urlhash_key'])) { - // We already have the urlhash field, so no need to migrate it. - return; - } - echo "\nFound old $table table, upgrading it to contain 'urlhash' field..."; - - $file = new File(); - $file->query(sprintf( - 'SELECT id, LEFT(url, 191) AS shortenedurl, COUNT(*) FROM %1$s ' . - 'WHERE LENGTH(url) > 191 GROUP BY id, shortenedurl HAVING COUNT(*) > 1', - common_database_tablename($table) - )); - print "\nFound {$file->N} URLs with too long entries in file table\n"; - while ($file->fetch()) { - // We've got a URL that is too long for our future file table - // so we'll cut it. We could save the original URL, but there is - // no guarantee it is complete anyway since the previous max was 255 chars. - $dupfile = new File(); - // First we find file entries that would be duplicates of this when shortened - // ... and we'll just throw the dupes out the window for now! It's already so borken. - $dupfile->query(sprintf('SELECT * FROM file WHERE LEFT(url, 191) = %1$s', $dupfile->_quote($file->shortenedurl))); - // Leave one of the URLs in the database by using ->find(true) (fetches first entry) - if ($dupfile->find(true)) { - print "\nShortening url entry for $table id: {$file->id} ["; - $orig = clone($dupfile); - $origurl = $dupfile->url; // save for logging purposes - $dupfile->url = $file->shortenedurl; // make sure it's only 191 chars from now on - $dupfile->update($orig); - print "\nDeleting duplicate entries of too long URL on $table id: {$file->id} ["; - // only start deleting with this fetch. - while ($dupfile->fetch()) { - common_log(LOG_INFO, sprintf('Deleting duplicate File entry of %1$d: %2$d (original URL: %3$s collides with these first 191 characters: %4$s', $dupfile->id, $file->id, $origurl, $file->shortenedurl)); - print "."; - $dupfile->delete(); - } - print "]\n"; - } else { - print "\nWarning! URL suddenly disappeared from database: {$file->url}\n"; - } - } - echo "...and now all the non-duplicates which are longer than 191 characters...\n"; - $file->query('UPDATE file SET url = LEFT(url, 191) WHERE LENGTH(url) > 191'); - - echo "\n...now running hacky pre-schemaupdate change for $table:"; - // We have to create a urlhash that is _not_ the primary key, - // transfer data and THEN run checkSchema - $schemadef['fields']['urlhash'] = array( - 'type' => 'varchar', - 'length' => 64, - 'not null' => false, // this is because when adding column, all entries will _be_ NULL! - 'description' => 'sha256 of destination URL (url field)', - ); - $schemadef['fields']['url'] = array( - 'type' => 'text', - 'description' => 'destination URL after following possible redirections', - ); - unset($schemadef['unique keys']); - $schema->ensureTable($table, $schemadef); - echo "DONE.\n"; - - $classname = ucfirst($table); - $tablefix = new $classname; - // urlhash is hash('sha256', $url) in the File table - echo "Updating urlhash fields in $table table..."; - switch (common_config('db', 'type')) { - case 'pgsql': - $url_sha256 = 'encode(sha256(CAST("url" AS bytea)), \'hex\')'; - break; - case 'mysql': - $url_sha256 = 'sha2(`url`, 256)'; - break; - default: - throw new ServerException('Unknown DB type selected.'); - } - $tablefix->query(sprintf( - 'UPDATE %1$s SET urlhash = %2$s, modified = CURRENT_TIMESTAMP;', - $tablefix->escapedTableName(), - $url_sha256 - )); - echo "DONE.\n"; - echo "Resuming core schema upgrade..."; } } diff --git a/src/Entity/FileRedirection.php b/src/Entity/FileRedirection.php new file mode 100644 index 0000000000..aaa46aeb2f --- /dev/null +++ b/src/Entity/FileRedirection.php @@ -0,0 +1,60 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for File redirects + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class FileRedirection +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'file_redirection', + 'fields' => [ + 'urlhash' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 hash of the URL'], + 'url' => ['type' => 'text', 'description' => 'short URL (or any other kind of redirect) for file (id)'], + 'file_id' => ['type' => 'int', 'description' => 'short URL for what URL/file'], + 'redirections' => ['type' => 'int', 'description' => 'redirect count'], + 'httpcode' => ['type' => 'int', 'description' => 'HTTP status code (20x, 30x, etc.)'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['urlhash'], + 'foreign keys' => [ + 'file_redirection_file_id_fkey' => ['file', ['file_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/FileThumbnail.php b/src/Entity/FileThumbnail.php new file mode 100644 index 0000000000..d30c7916a0 --- /dev/null +++ b/src/Entity/FileThumbnail.php @@ -0,0 +1,65 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for File thumbnails + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class FileThumbnail +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'file_thumbnail', + 'fields' => [ + 'file_id' => ['type' => 'int', 'not null' => true, 'description' => 'thumbnail for what URL/file'], + 'urlhash' => ['type' => 'varchar', 'length' => 64, 'description' => 'sha256 of url field if non-empty'], + 'url' => ['type' => 'text', 'description' => 'URL of thumbnail'], + 'filename' => ['type' => 'text', 'description' => 'if stored locally, filename is put here'], + 'width' => ['type' => 'int', 'not null' => true, 'description' => 'width of thumbnail'], + 'height' => ['type' => 'int', 'not null' => true, 'description' => 'height of thumbnail'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['file_id', 'width', 'height'], + 'indexes' => [ + 'file_thumbnail_file_id_idx' => ['file_id'], + 'file_thumbnail_urlhash_idx' => ['urlhash'], + ], + 'foreign keys' => [ + 'file_thumbnail_file_id_fkey' => ['file', ['file_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/FileToPost.php b/src/Entity/FileToPost.php new file mode 100644 index 0000000000..2cfcbd14e5 --- /dev/null +++ b/src/Entity/FileToPost.php @@ -0,0 +1,62 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for relating a file to a post + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class FileToPost +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'file_to_post', + 'fields' => [ + 'file_id' => ['type' => 'int', 'not null' => true, 'description' => 'id of URL/file'], + 'post_id' => ['type' => 'int', 'not null' => true, 'description' => 'id of the notice it belongs to'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['file_id', 'post_id'], + 'foreign keys' => [ + 'file_to_post_file_id_fkey' => ['file', ['file_id' => 'id']], + 'file_to_post_post_id_fkey' => ['notice', ['post_id' => 'id']], + ], + 'indexes' => [ + 'file_id_idx' => ['file_id'], + 'post_id_idx' => ['post_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/File_redirection.php b/src/Entity/File_redirection.php deleted file mode 100644 index 50c6df4bc7..0000000000 --- a/src/Entity/File_redirection.php +++ /dev/null @@ -1,490 +0,0 @@ -. - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for file_redirection - */ -class File_redirection extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'file_redirection'; // table name - public $urlhash; // varchar(64) primary_key not_null - public $url; // text - public $file_id; // int(4) - public $redirections; // int(4) - public $httpcode; // int(4) - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - protected $file; /* Cache the associated file sometimes */ - - public static function schemaDef() - { - return array( - 'fields' => array( - 'urlhash' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 hash of the URL'), - 'url' => array('type' => 'text', 'description' => 'short URL (or any other kind of redirect) for file (id)'), - 'file_id' => array('type' => 'int', 'description' => 'short URL for what URL/file'), - 'redirections' => array('type' => 'int', 'description' => 'redirect count'), - 'httpcode' => array('type' => 'int', 'description' => 'HTTP status code (20x, 30x, etc.)'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('urlhash'), - 'foreign keys' => array( - 'file_redirection_file_id_fkey' => array('file', array('file_id' => 'id')), - ), - 'indexes' => array( - 'file_redirection_file_id_idx' => array('file_id'), - ), - ); - } - - public static function getByUrl($url) - { - return self::getByPK(array('urlhash' => File::hashurl($url))); - } - - public static function _commonHttp($url, $redirs) - { - $request = new HTTPClient($url); - $request->setConfig(array( - 'connect_timeout' => 10, // # seconds to wait - 'max_redirs' => $redirs, // # max number of http redirections to follow - 'follow_redirects' => false, // We follow redirects ourselves in lib/httpclient.php - 'store_body' => false, // We won't need body content here. - )); - return $request; - } - - /** - * Check if this URL is a redirect and return redir info. - * - * Most code should call File_redirection::where instead, to check if we - * already know that redirection and avoid extra hits to the web. - * - * The URL is hit and any redirects are followed, up to 10 levels or until - * a protected URL is reached. - * - * @param string $in_url - * @return mixed one of: - * string - target URL, if this is a direct link or can't be followed - * array - redirect info if this is an *unknown* redirect: - * associative array with the following elements: - * code: HTTP status code - * redirects: count of redirects followed - * url: URL string of final target - * type (optional): MIME type from Content-Type header - * size (optional): byte size from Content-Length header - * time (optional): timestamp from Last-Modified header - */ - public static function lookupWhere($short_url, $redirs = 10, $protected = false) - { - if ($redirs < 0) { - return false; - } - - if (strpos($short_url, '://') === false) { - return $short_url; - } - try { - $request = self::_commonHttp($short_url, $redirs); - // Don't include body in output - $request->setMethod(HTTP_Request2::METHOD_HEAD); - $response = $request->send(); - - if (405 == $response->getStatus() || 204 == $response->getStatus()) { - // HTTP 405 Unsupported Method - // Server doesn't support HEAD method? Can this really happen? - // We'll try again as a GET and ignore the response data. - // - // HTTP 204 No Content - // YFrog sends 204 responses back for our HEAD checks, which - // seems like it may be a logic error in their servers. If - // we get a 204 back, re-run it as a GET... if there's really - // no content it'll be cheap. :) - $request = self::_commonHttp($short_url, $redirs); - $response = $request->send(); - } elseif (400 == $response->getStatus()) { - throw new Exception('Got error 400 on HEAD request, will not go further.'); - } - } catch (Exception $e) { - // Invalid URL or failure to reach server - common_log(LOG_ERR, "Error while following redirects for $short_url: " . $e->getMessage()); - return $short_url; - } - - // if last url after all redirections is protected, - // use the url before it in the redirection chain - if ($response->getRedirectCount() && File::isProtected($response->getEffectiveUrl())) { - $return_url = $response->redirUrls[$response->getRedirectCount() - 1]; - } else { - $return_url = $response->getEffectiveUrl(); - } - - $ret = array('code' => $response->getStatus() - , 'redirects' => $response->getRedirectCount() - , 'url' => $return_url); - - $type = $response->getHeader('Content-Type'); - if ($type) { - $ret['type'] = $type; - } - if ($protected) { - $ret['protected'] = true; - } - $size = $response->getHeader('Content-Length'); // @fixme bytes? - if ($size) { - $ret['size'] = $size; - } - $time = $response->getHeader('Last-Modified'); - if ($time) { - $ret['time'] = strtotime($time); - } - return $ret; - } - - /** - * Check if this URL is a redirect and return redir info. - * If a File record is present for this URL, it is not considered a redirect. - * If a File_redirection record is present for this URL, the recorded target is returned. - * - * If no File or File_redirect record is present, the URL is hit and any - * redirects are followed, up to 10 levels or until a protected URL is - * reached. - * - * @param string $in_url - * @param boolean $discover true to attempt dereferencing the redirect if we don't know it already - * @return File_redirection - */ - public static function where($in_url, $discover = true) - { - $redir = new File_redirection(); - $redir->url = $in_url; - $redir->urlhash = File::hashurl($redir->url); - $redir->redirections = 0; - - try { - $r = File_redirection::getByUrl($in_url); - - try { - $f = File::getByID($r->file_id); - $r->file = $f; - $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(); - return self::where($in_url); - } - - // File_redirecion and File record found, return both - return $r; - } catch (NoResultException $e) { - // File_redirecion record not found, but this might be a direct link to a file - try { - $f = File::getByUrl($in_url); - $redir->file_id = $f->id; - $redir->file = $f; - return $redir; - } catch (NoResultException $e) { - // nope, this was not a direct link to a file either, let's keep going - } - } - - if ($discover) { - // try to follow redirects and get the final url - $redir_info = File_redirection::lookupWhere($in_url); - if (is_string($redir_info)) { - $redir_info = array('url' => $redir_info); - } - - // the last url in the redirection chain can actually be a redirect! - // this is the case with local /attachment/{file_id} links - // in that case we have the file id already - try { - $r = File_redirection::getByUrl($redir_info['url']); - - $f = File::getKV('id', $r->file_id); - - if ($f instanceof File) { - $redir->file = $f; - $redir->redir_url = $f->url; - } else { - // Invalid entry in File_redirection, 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 trying again...' - ); - $r->delete(); - return self::where($in_url); - } - } catch (NoResultException $e) { - // save the file now when we know that we don't have it in File_redirection - try { - $redir->file = File::saveNew($redir_info, $redir_info['url']); - } catch (ServerException $e) { - common_log(LOG_ERR, $e); - } - } - - // If this is a redirection and we have a file to redirect to, save it - // (if it doesn't exist in File_redirection already) - if ($redir->file instanceof File && $redir_info['url'] != $in_url) { - try { - $file_redir = File_redirection::getByUrl($in_url); - } catch (NoResultException $e) { - $file_redir = new File_redirection(); - $file_redir->urlhash = File::hashurl($in_url); - $file_redir->url = $in_url; - $file_redir->file_id = $redir->file->getID(); - $file_redir->insert(); - $file_redir->redir_url = $redir->file->url; - } - - $file_redir->file = $redir->file; - return $file_redir; - } - } - - return $redir; - } - - /** - * Shorten a URL with the current user's configured shortening - * options, if applicable. - * - * If it cannot be shortened or the "short" URL is longer than the - * original, the original is returned. - * - * If the referenced item has not been seen before, embedding data - * may be saved. - * - * @param string $long_url - * @param User $user whose shortening options to use; defaults to the current web session user - * @return string - */ - public static function makeShort($long_url, $user = null) - { - $canon = File_redirection::_canonUrl($long_url); - - $short_url = File_redirection::_userMakeShort($canon, $user); - - // Did we get one? Is it shorter? - - return !empty($short_url) ? $short_url : $long_url; - } - - /** - * Shorten a URL with the current user's configured shortening - * options, if applicable. - * - * If it cannot be shortened or the "short" URL is longer than the - * original, the original is returned. - * - * If the referenced item has not been seen before, embedding data - * may be saved. - * - * @param string $long_url - * @return string - */ - - public static function forceShort($long_url, $user) - { - $canon = File_redirection::_canonUrl($long_url); - - $short_url = File_redirection::_userMakeShort($canon, $user, true); - - // Did we get one? Is it shorter? - return !empty($short_url) ? $short_url : $long_url; - } - - public static function _userMakeShort($long_url, User $user = null, $force = false) - { - $short_url = common_shorten_url($long_url, $user, $force); - if (!empty($short_url) && $short_url != $long_url) { - $short_url = (string)$short_url; - // store it - try { - $file = File::getByUrl($long_url); - } catch (NoResultException $e) { - // Check if the target URL is itself a redirect... - // This should already have happened in processNew in common_shorten_url() - $redir = File_redirection::where($long_url); - $file = $redir->file; - } - // Now we definitely have a File object in $file - try { - $file_redir = File_redirection::getByUrl($short_url); - } catch (NoResultException $e) { - $file_redir = new File_redirection(); - $file_redir->urlhash = File::hashurl($short_url); - $file_redir->url = $short_url; - $file_redir->file_id = $file->getID(); - $file_redir->insert(); - } - return $short_url; - } - return null; - } - - /** - * Basic attempt to canonicalize a URL, cleaning up some standard variants - * such as funny syntax or a missing path. Used internally when cleaning - * up URLs for storage and following redirect chains. - * - * Note that despite being on File_redirect, this function DOES NOT perform - * any dereferencing of redirects. - * - * @param string $in_url input URL - * @param string $default_scheme if given a bare link; defaults to 'http://' - * @return string - */ - public static function _canonUrl($in_url, $default_scheme = 'http://') - { - if (empty($in_url)) { - return false; - } - $out_url = $in_url; - $p = parse_url($out_url); - if (empty($p['host']) || empty($p['scheme'])) { - list($scheme) = explode(':', $in_url, 2); - switch (strtolower($scheme)) { - case 'fax': - case 'tel': - $out_url = str_replace('.-()', '', $out_url); - break; - - // non-HTTP schemes, so no redirects - case 'bitcoin': - case 'mailto': - case 'aim': - case 'jabber': - case 'xmpp': - // don't touch anything - break; - - // URLs without domain name, so no redirects - case 'magnet': - // don't touch anything - break; - - // URLs with coordinates, not browsable domain names - case 'geo': - // don't touch anything - break; - - default: - $out_url = $default_scheme . ltrim($out_url, '/'); - $p = parse_url($out_url); - if (empty($p['scheme'])) { - return false; - } - break; - } - } - - if (('ftp' == $p['scheme']) || ('ftps' == $p['scheme']) || ('http' == $p['scheme']) || ('https' == $p['scheme'])) { - if (empty($p['host'])) { - return false; - } - if (empty($p['path'])) { - $out_url .= '/'; - } - } - - return $out_url; - } - - public static function saveNew($data, $file_id, $url) - { - $file_redir = new File_redirection; - $file_redir->urlhash = File::hashurl($url); - $file_redir->url = $url; - $file_redir->file_id = $file_id; - $file_redir->redirections = intval($data['redirects']); - $file_redir->httpcode = intval($data['code']); - $file_redir->insert(); - } - - public static function beforeSchemaUpdate() - { - $table = strtolower(get_called_class()); - $schema = Schema::get(); - $schemadef = $schema->getTableDef($table); - - // 2015-02-19 We have to upgrade our table definitions to have the urlhash field populated - if (isset($schemadef['fields']['urlhash']) && in_array('urlhash', $schemadef['primary key'])) { - // We already have the urlhash field, so no need to migrate it. - return; - } - echo "\nFound old $table table, upgrading it to contain 'urlhash' field..."; - // We have to create a urlhash that is _not_ the primary key, - // transfer data and THEN run checkSchema - $schemadef['fields']['urlhash'] = [ - 'type' => 'varchar', - 'length' => 64, - 'not null' => true, - 'description' => 'sha256 hash of the URL', - ]; - $schemadef['fields']['url'] = [ - 'type' => 'text', - 'description' => 'short URL (or any other kind of redirect) for file (id)', - ]; - unset($schemadef['primary key']); - $schema->ensureTable($table, $schemadef); - echo "DONE.\n"; - - $classname = ucfirst($table); - $tablefix = new $classname; - // urlhash is hash('sha256', $url) in the File table - echo "Updating urlhash fields in $table table..."; - switch (common_config('db', 'type')) { - case 'pgsql': - $url_sha256 = 'encode(sha256(CAST("url" AS bytea)), \'hex\')'; - break; - case 'mysql': - $url_sha256 = 'sha2(`url`, 256)'; - break; - default: - throw new ServerException('Unknown DB type selected.'); - } - $tablefix->query(sprintf( - 'UPDATE %1$s SET urlhash = %2$s, modified = CURRENT_TIMESTAMP;', - $tablefix->escapedTableName(), - $url_sha256 - )); - echo "DONE.\n"; - echo "Resuming core schema upgrade..."; - } - - public function getFile() - { - if (!$this->file instanceof File) { - $this->file = File::getByID($this->file_id); - } - - return $this->file; - } -} diff --git a/src/Entity/File_to_post.php b/src/Entity/File_to_post.php deleted file mode 100644 index 8906a0e6e2..0000000000 --- a/src/Entity/File_to_post.php +++ /dev/null @@ -1,114 +0,0 @@ -. - -/** - * Table Definition for file_to_post - * - * @copyright 2008, 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class File_to_post extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'file_to_post'; // table name - public $file_id; // int(4) primary_key not_null - public $post_id; // int(4) primary_key not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'id of URL/file'), - 'post_id' => array('type' => 'int', 'not null' => true, 'description' => 'id of the notice it belongs to'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('file_id', 'post_id'), - 'foreign keys' => array( - 'file_to_post_file_id_fkey' => array('file', array('file_id' => 'id')), - 'file_to_post_post_id_fkey' => array('notice', array('post_id' => 'id')), - ), - 'indexes' => array( - 'file_to_post_post_id_idx' => array('post_id'), - ), - ); - } - - public static function processNew(File $file, Notice $notice) - { - static $seen = array(); - - $file_id = $file->getID(); - $notice_id = $notice->getID(); - if (!array_key_exists($notice_id, $seen)) { - $seen[$notice_id] = array(); - } - - if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) { - try { - $f2p = File_to_post::getByPK(array('post_id' => $notice_id, - 'file_id' => $file_id)); - } catch (NoResultException $e) { - $f2p = new File_to_post; - $f2p->file_id = $file_id; - $f2p->post_id = $notice_id; - $f2p->insert(); - - $file->blowCache(); - } - - $seen[$notice_id][] = $file_id; - } - } - - public static function getNoticeIDsByFile(File $file) - { - $f2p = new File_to_post(); - - $f2p->selectAdd(); - $f2p->selectAdd('post_id'); - - $f2p->file_id = $file->getID(); - - $ids = array(); - - if (!$f2p->find()) { - throw new NoResultException($f2p); - } - - return $f2p->fetchAll('post_id'); - } - - public function delete($useWhere = false) - { - try { - $f = File::getByID($this->file_id); - $f->blowCache(); - } catch (NoResultException $e) { - // ...alright, that's weird, but no File to delete anyway. - } - - return parent::delete($useWhere); - } -} diff --git a/src/Entity/ForeignLink.php b/src/Entity/ForeignLink.php new file mode 100644 index 0000000000..2981526006 --- /dev/null +++ b/src/Entity/ForeignLink.php @@ -0,0 +1,70 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user's foreign profile + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ForeignLink +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'foreign_link', + 'fields' => [ + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'link to user on this system, if exists'], + 'foreign_id' => ['type' => 'int', 'size' => 'big', 'unsigned' => true, 'not null' => true, 'description' => 'link to user on foreign service, if exists'], + 'service' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to service'], + 'credentials' => ['type' => 'varchar', 'length' => 191, 'description' => 'authc credentials, typically a password'], + 'noticesync' => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies'], + 'friendsync' => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 2, 'description' => 'friend synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'], + 'profilesync' => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'], + 'last_noticesync' => ['type' => 'datetime', 'description' => 'last time notices were imported'], + 'last_friendsync' => ['type' => 'datetime', 'description' => 'last time friends were imported'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['user_id', 'foreign_id', 'service'], + 'foreign keys' => [ + 'foreign_link_user_id_fkey' => ['user', ['user_id' => 'id']], + 'foreign_link_foreign_id_fkey' => ['foreign_user', ['foreign_id' => 'id', 'service' => 'service']], + 'foreign_link_service_fkey' => ['foreign_service', ['service' => 'id']], + ], + 'indexes' => [ + 'foreign_user_user_id_idx' => ['user_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ForeignService.php b/src/Entity/ForeignService.php new file mode 100644 index 0000000000..8e64542926 --- /dev/null +++ b/src/Entity/ForeignService.php @@ -0,0 +1,59 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for foreign services + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ForeignService +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'foreign_service', + 'fields' => [ + 'id' => ['type' => 'int', 'not null' => true, 'description' => 'numeric key for service'], + 'name' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'name of the service'], + 'description' => ['type' => 'varchar', 'length' => 191, 'description' => 'description'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'foreign_service_name_key' => ['name'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ForeignSubscription.php b/src/Entity/ForeignSubscription.php new file mode 100644 index 0000000000..6c5c953140 --- /dev/null +++ b/src/Entity/ForeignSubscription.php @@ -0,0 +1,65 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user's foreign subscriptions + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ForeignSubscription +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'foreign_subscription', + + 'fields' => [ + 'service' => ['type' => 'int', 'not null' => true, 'description' => 'service where relationship happens'], + 'subscriber' => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'subscriber on foreign service'], + 'subscribed' => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'subscribed user'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + ], + 'primary key' => ['service', 'subscriber', 'subscribed'], + 'foreign keys' => [ + 'foreign_subscription_service_fkey' => ['foreign_service', ['service' => 'id']], + 'foreign_subscription_subscriber_fkey' => ['foreign_user', ['subscriber' => 'id', 'service' => 'service']], + 'foreign_subscription_subscribed_fkey' => ['foreign_user', ['subscribed' => 'id', 'service' => 'service']], + ], + 'indexes' => [ + 'foreign_subscription_subscriber_idx' => ['service', 'subscriber'], + 'foreign_subscription_subscribed_idx' => ['service', 'subscribed'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ForeignUser.php b/src/Entity/ForeignUser.php new file mode 100644 index 0000000000..a00c8e8e15 --- /dev/null +++ b/src/Entity/ForeignUser.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Foreign Users + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ForeignUser +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'foreign_user', + 'fields' => [ + 'id' => ['type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'unique numeric key on foreign service'], + 'service' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to service'], + 'uri' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'identifying URI'], + 'nickname' => ['type' => 'varchar', 'length' => 191, 'description' => 'nickname on foreign service'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id', 'service'], + 'foreign keys' => [ + 'foreign_user_service_fkey' => ['foreign_service', ['service' => 'id']], + ], + 'unique keys' => [ + 'foreign_user_uri_key' => ['uri'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Foreign_link.php b/src/Entity/Foreign_link.php deleted file mode 100644 index a12a5f4a69..0000000000 --- a/src/Entity/Foreign_link.php +++ /dev/null @@ -1,187 +0,0 @@ -. - -/** - * Table Definition for foreign_link - */ - -defined('GNUSOCIAL') || die(); - -class Foreign_link extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'foreign_link'; // table name - public $user_id; // int(4) primary_key not_null - public $foreign_id; // bigint(8) primary_key not_null unsigned - public $service; // int(4) primary_key not_null - public $credentials; // blob - public $noticesync; // tinyint(1) not_null default_1 - public $friendsync; // tinyint(1) not_null default_2 - public $profilesync; // tinyint(1) not_null default_1 - public $last_noticesync; // datetime() - public $last_friendsync; // datetime() - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'link to user on this system, if exists'), - 'foreign_id' => array('type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'link to user on foreign service, if exists'), - 'service' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to service'), - 'credentials' => array('type' => 'blob', 'description' => 'authc credentials, typically a password'), - 'noticesync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies'), - 'friendsync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 2, 'description' => 'friend synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'), - 'profilesync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'), - 'last_noticesync' => array('type' => 'datetime', 'description' => 'last time notices were imported'), - 'last_friendsync' => array('type' => 'datetime', 'description' => 'last time friends were imported'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('user_id', 'foreign_id', 'service'), - 'foreign keys' => array( - 'foreign_link_user_id_fkey' => array('user', array('user_id' => 'id')), - 'foreign_link_foreign_id_service_fkey' => array('foreign_user', array('foreign_id' => 'id', 'service' => 'service')), - 'foreign_link_service_fkey' => array('foreign_service', array('service' => 'id')), - ), - 'indexes' => array( - 'foreign_link_foreign_id_service_idx' => array('foreign_id', 'service'), - 'foreign_link_service_idx' => array('service'), - ), - ); - } - - public static function getByUserID($user_id, $service) - { - if (empty($user_id) || empty($service)) { - throw new ServerException('Empty user_id or service for Foreign_link::getByUserID'); - } - - $flink = new Foreign_link(); - $flink->service = $service; - $flink->user_id = $user_id; - $flink->limit(1); - - if (!$flink->find(true)) { - throw new NoResultException($flink); - } - - return $flink; - } - - public static function getByForeignID($foreign_id, $service) - { - if (empty($foreign_id) || empty($service)) { - throw new ServerException('Empty foreign_id or service for Foreign_link::getByForeignID'); - } - - $flink = new Foreign_link(); - $flink->service = $service; - $flink->foreign_id = $foreign_id; - $flink->limit(1); - - if (!$flink->find(true)) { - throw new NoResultException($flink); - } - - return $flink; - } - - public function set_flags($noticesend, $noticerecv, $replysync, $repeatsync, $friendsync) - { - if ($noticesend) { - $this->noticesync |= FOREIGN_NOTICE_SEND; - } else { - $this->noticesync &= ~FOREIGN_NOTICE_SEND; - } - - if ($noticerecv) { - $this->noticesync |= FOREIGN_NOTICE_RECV; - } else { - $this->noticesync &= ~FOREIGN_NOTICE_RECV; - } - - if ($replysync) { - $this->noticesync |= FOREIGN_NOTICE_SEND_REPLY; - } else { - $this->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY; - } - - if ($repeatsync) { - $this->noticesync |= FOREIGN_NOTICE_SEND_REPEAT; - } else { - $this->noticesync &= ~FOREIGN_NOTICE_SEND_REPEAT; - } - - if ($friendsync) { - $this->friendsync |= FOREIGN_FRIEND_RECV; - } else { - $this->friendsync &= ~FOREIGN_FRIEND_RECV; - } - - $this->profilesync = 0; - } - - // Convenience methods - public function getForeignUser() - { - $fuser = new Foreign_user(); - $fuser->service = $this->service; - $fuser->id = $this->foreign_id; - - $fuser->limit(1); - - if (!$fuser->find(true)) { - throw new NoResultException($fuser); - } - - return $fuser; - } - - public function getUser() - { - return Profile::getByID($this->user_id)->getUser(); - } - - public function getProfile() - { - return Profile::getByID($this->user_id); - } - - // Make sure we only ever delete one record at a time - public function safeDelete() - { - if (!empty($this->user_id) - && !empty($this->foreign_id) - && !empty($this->service)) { - return $this->delete(); - } else { - common_debug( - LOG_WARNING, - 'Foreign_link::safeDelete() tried to delete a ' - . 'Foreign_link without a fully specified compound key: ' - . var_export($this, true) - ); - return false; - } - } -} diff --git a/src/Entity/Foreign_service.php b/src/Entity/Foreign_service.php deleted file mode 100644 index 64ea7cbf01..0000000000 --- a/src/Entity/Foreign_service.php +++ /dev/null @@ -1,54 +0,0 @@ -. - -/** - * Table Definition for foreign_service - */ - -defined('GNUSOCIAL') || die(); - -class Foreign_service extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'foreign_service'; // table name - public $id; // int(4) primary_key not_null - public $name; // varchar(32) unique_key not_null - public $description; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'int', 'not null' => true, 'description' => 'numeric key for service'), - 'name' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'name of the service'), - 'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'foreign_service_name_key' => array('name'), - ), - ); - } -} diff --git a/src/Entity/Foreign_subscription.php b/src/Entity/Foreign_subscription.php deleted file mode 100644 index e1f14fc846..0000000000 --- a/src/Entity/Foreign_subscription.php +++ /dev/null @@ -1,60 +0,0 @@ -. - -/** - * Table Definition for foreign_subscription - */ - -defined('GNUSOCIAL') || die(); - -class Foreign_subscription extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'foreign_subscription'; // table name - public $service; // int(4) primary_key not_null - public $subscriber; // int(4) primary_key not_null - public $subscribed; // int(4) primary_key not_null - public $created; // datetime() - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - - 'fields' => array( - 'service' => array('type' => 'int', 'not null' => true, 'description' => 'service where relationship happens'), - 'subscriber' => array('type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'subscriber on foreign service'), - 'subscribed' => array('type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'subscribed user'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - ), - 'primary key' => array('service', 'subscriber', 'subscribed'), - 'foreign keys' => array( - 'foreign_subscription_service_fkey' => array('foreign_service', array('service' => 'id')), - 'foreign_subscription_subscriber_service_fkey' => array('foreign_user', array('subscriber' => 'id', 'service' => 'service')), - 'foreign_subscription_subscribed_service_fkey' => array('foreign_user', array('subscribed' => 'id', 'service' => 'service')), - ), - 'indexes' => array( - 'foreign_subscription_subscriber_service_idx' => array('subscriber', 'service'), - 'foreign_subscription_subscribed_service_idx' => array('subscribed', 'service'), - 'foreign_subscription_service_subscribed_idx' => array('service', 'subscribed'), - ), - ); - } -} diff --git a/src/Entity/Foreign_user.php b/src/Entity/Foreign_user.php deleted file mode 100644 index dfe65b8c4f..0000000000 --- a/src/Entity/Foreign_user.php +++ /dev/null @@ -1,98 +0,0 @@ -. - -/** - * Table Definition for foreign_user - */ - -defined('GNUSOCIAL') || die(); - -class Foreign_user extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'foreign_user'; // table name - public $id; // bigint(8) primary_key not_null - public $service; // int(4) primary_key not_null - public $uri; // varchar(191) unique_key not_null not 255 because utf8mb4 takes more space - public $nickname; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'unique numeric key on foreign service'), - 'service' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to service'), - 'uri' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'identifying URI'), - 'nickname' => array('type' => 'varchar', 'length' => 191, 'description' => 'nickname on foreign service'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id', 'service'), - 'foreign keys' => array( - 'foreign_user_service_fkey' => array('foreign_service', array('service' => 'id')), - ), - 'unique keys' => array( - 'foreign_user_uri_key' => array('uri'), - ), - 'indexes' => array( - 'foreign_user_service_idx' => array('service'), - ), - ); - } - - public static function getForeignUser($id, $service) - { - if (empty($id) || empty($service)) { - throw new ServerException('Empty foreign user id or service for Foreign_user::getForeignUser'); - } - - $fuser = new Foreign_user(); - $fuser->id = $id; - $fuser->service = $service; - $fuser->limit(1); - - if (!$fuser->find(true)) { - throw new NoResultException($fuser); - } - - return $fuser; - } - - public static function getByNickname($nickname, $service) - { - if (empty($nickname) || empty($service)) { - throw new ServerException('Empty nickname or service for Foreign_user::getByNickname'); - } - - $fuser = new Foreign_user(); - $fuser->service = $service; - $fuser->nickname = $nickname; - $fuser->limit(1); - - if (!$fuser->find(true)) { - throw new NoResultException($fuser); - } - - return $fuser; - } -} diff --git a/src/Entity/GS_DataObject.php b/src/Entity/GS_DataObject.php deleted file mode 100644 index 4ca2436eec..0000000000 --- a/src/Entity/GS_DataObject.php +++ /dev/null @@ -1,234 +0,0 @@ -. + }}} */ + +namespace App\Entity; + +/** + * Entity for Group Alias + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class GroupAlias +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'group_alias', + 'fields' => [ + 'alias' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'additional nickname for the group'], + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'group profile is blocked from'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date alias was created'], + ], + 'primary key' => ['alias'], + 'foreign keys' => [ + 'group_alias_group_id_fkey' => ['user_group', ['group_id' => 'id']], + ], + 'indexes' => [ + 'group_alias_group_id_idx' => ['group_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/GroupBlock.php b/src/Entity/GroupBlock.php new file mode 100644 index 0000000000..1c17464c19 --- /dev/null +++ b/src/Entity/GroupBlock.php @@ -0,0 +1,60 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Group Block + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class GroupBlock +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'group_block', + 'fields' => [ + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'group profile is blocked from'], + 'blocked' => ['type' => 'int', 'not null' => true, 'description' => 'profile that is blocked'], + 'blocker' => ['type' => 'int', 'not null' => true, 'description' => 'user making the block'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date of blocking'], + ], + 'primary key' => ['group_id', 'blocked'], + 'foreign keys' => [ + 'group_block_group_id_fkey' => ['user_group', ['group_id' => 'id']], + 'group_block_blocked_fkey' => ['profile', ['blocked' => 'id']], + 'group_block_blocker_fkey' => ['user', ['blocker' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/GroupInbox.php b/src/Entity/GroupInbox.php new file mode 100644 index 0000000000..fc08908c62 --- /dev/null +++ b/src/Entity/GroupInbox.php @@ -0,0 +1,64 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Group's inbox + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class GroupInbox +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'group_inbox', + 'description' => 'Many-many table listing notices posted to a given group, or which groups a given notice was posted to.', + 'fields' => [ + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'group receiving the message'], + 'notice_id' => ['type' => 'int', 'not null' => true, 'description' => 'notice received'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date the notice was created'], + ], + 'primary key' => ['group_id', 'notice_id'], + 'foreign keys' => [ + 'group_inbox_group_id_fkey' => ['user_group', ['group_id' => 'id']], + 'group_inbox_notice_id_fkey' => ['notice', ['notice_id' => 'id']], + ], + 'indexes' => [ + 'group_inbox_created_idx' => ['created'], + 'group_inbox_notice_id_idx' => ['notice_id'], + 'group_inbox_group_id_created_notice_id_idx' => ['group_id', 'created', 'notice_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/GroupJoinQueue.php b/src/Entity/GroupJoinQueue.php new file mode 100644 index 0000000000..cfad97f130 --- /dev/null +++ b/src/Entity/GroupJoinQueue.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Queue on joining a group + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class GroupJoinQueue +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'group_join_queue', + 'description' => 'Holder for group join requests awaiting moderation.', + 'fields' => [ + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'], + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'remote or local group to join, if any'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + ], + 'primary key' => ['profile_id', 'group_id'], + 'indexes' => [ + 'group_join_queue_profile_id_created_idx' => ['profile_id', 'created'], + 'group_join_queue_group_id_created_idx' => ['group_id', 'created'], + ], + 'foreign keys' => [ + 'group_join_queue_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + 'group_join_queue_group_id_fkey' => ['user_group', ['group_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/GroupMember.php b/src/Entity/GroupMember.php new file mode 100644 index 0000000000..ec5d2bae2c --- /dev/null +++ b/src/Entity/GroupMember.php @@ -0,0 +1,71 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for a Group Member + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class GroupMember +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'group_member', + 'fields' => [ + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'], + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'], + 'is_admin' => ['type' => 'bool', 'default' => false, 'description' => 'is this user an admin?'], + 'uri' => ['type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['group_id', 'profile_id'], + 'unique keys' => [ + 'group_member_uri_key' => ['uri'], + ], + 'foreign keys' => [ + 'group_member_group_id_fkey' => ['user_group', ['group_id' => 'id']], + 'group_member_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + // @fixme probably we want a (profile_id, created) index here? + 'group_member_profile_id_idx' => ['profile_id'], + 'group_member_created_idx' => ['created'], + 'group_member_profile_id_created_idx' => ['profile_id', 'created'], + 'group_member_group_id_created_idx' => ['group_id', 'created'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Group_alias.php b/src/Entity/Group_alias.php deleted file mode 100644 index 9ee773d5b7..0000000000 --- a/src/Entity/Group_alias.php +++ /dev/null @@ -1,65 +0,0 @@ -. - -/** - * Table Definition for group_alias - * - * @copyright 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Group_alias extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'group_alias'; // table name - public $alias; // varchar(64) primary_key not_null - public $group_id; // int(4) not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'alias' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'additional nickname for the group'), - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'group profile is blocked from'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date alias was created'), - ), - 'primary key' => array('alias'), - 'foreign keys' => array( - 'group_alias_group_id_fkey' => array('user_group', array('group_id' => 'id')), - ), - 'indexes' => array( - 'group_alias_group_id_idx' => array('group_id'), - ), - ); - } - - public function getProfile() - { - $group = User_group::getKV('id', $this->group_id); - if (!($group instanceof User_group)) { - return null; // TODO: Throw exception when other code is ready - } - return $group->getProfile(); - } -} diff --git a/src/Entity/Group_block.php b/src/Entity/Group_block.php deleted file mode 100644 index 03dac31e19..0000000000 --- a/src/Entity/Group_block.php +++ /dev/null @@ -1,132 +0,0 @@ -. - -/** - * Table Definition for group_block - * - * @package GNUsocial - * @author Evan Prodromou - * @copyright 2008, 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Group_block extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'group_block'; // table name - public $group_id; // int(4) primary_key not_null - public $blocked; // int(4) primary_key not_null - public $blocker; // int(4) not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'group profile is blocked from'), - 'blocked' => array('type' => 'int', 'not null' => true, 'description' => 'profile that is blocked'), - 'blocker' => array('type' => 'int', 'not null' => true, 'description' => 'user making the block'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date of blocking'), - ), - 'primary key' => array('group_id', 'blocked'), - 'foreign keys' => array( - 'group_block_group_id_fkey' => array('user_group', array('group_id' => 'id')), - 'group_block_blocked_fkey' => array('profile', array('blocked' => 'id')), - 'group_block_blocker_fkey' => array('user', array('blocker' => 'id')), - ), - 'indexes' => array( - 'group_block_blocked_idx' => array('blocked'), - 'group_block_blocker_idx' => array('blocker'), - ), - ); - } - - public static function isBlocked($group, $profile) - { - $block = Group_block::pkeyGet([ - 'group_id' => $group->id, - 'blocked' => $profile->id, - ]); - return !empty($block); - } - - public static function blockProfile($group, $profile, $blocker) - { - // Insert the block - - $block = new Group_block(); - - $block->query('START TRANSACTION'); - - $block->group_id = $group->id; - $block->blocked = $profile->id; - $block->blocker = $blocker->id; - - $result = $block->insert(); - - if ($result === false) { - common_log_db_error($block, 'INSERT', __FILE__); - return null; - } - - // Delete membership if any - - $member = new Group_member(); - - $member->group_id = $group->id; - $member->profile_id = $profile->id; - - if ($member->find(true)) { - $result = $member->delete(); - if ($result === false) { - common_log_db_error($member, 'DELETE', __FILE__); - return null; - } - } - - // Commit, since both have been done - - $block->query('COMMIT'); - - return $block; - } - - public static function unblockProfile($group, $profile) - { - $block = Group_block::pkeyGet(array('group_id' => $group->id, - 'blocked' => $profile->id)); - - if (empty($block)) { - return null; - } - - $result = $block->delete(); - - if (!$result) { - common_log_db_error($block, 'DELETE', __FILE__); - return null; - } - - return true; - } -} diff --git a/src/Entity/Group_inbox.php b/src/Entity/Group_inbox.php deleted file mode 100644 index 36333226df..0000000000 --- a/src/Entity/Group_inbox.php +++ /dev/null @@ -1,57 +0,0 @@ -. - -/** - * Table Definition for group_inbox - */ - -defined('GNUSOCIAL') || die(); - -class Group_inbox extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'group_inbox'; // table name - public $group_id; // int(4) primary_key not_null - public $notice_id; // int(4) primary_key not_null - public $created; // datetime() - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'description' => 'Many-many table listing notices posted to a given group, or which groups a given notice was posted to.', - 'fields' => array( - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'group receiving the message'), - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice received'), - 'created' => array('type' => 'datetime', 'description' => 'date the notice was created'), - ), - 'primary key' => array('group_id', 'notice_id'), - 'foreign keys' => array( - 'group_inbox_group_id_fkey' => array('user_group', array('group_id' => 'id')), - 'group_inbox_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - ), - 'indexes' => array( - 'group_inbox_created_idx' => array('created'), - 'group_inbox_notice_id_idx' => array('notice_id'), - 'group_inbox_group_id_created_notice_id_idx' => array('group_id', 'created', 'notice_id'), - ), - ); - } -} diff --git a/src/Entity/Group_join_queue.php b/src/Entity/Group_join_queue.php deleted file mode 100644 index 6517ce2cf7..0000000000 --- a/src/Entity/Group_join_queue.php +++ /dev/null @@ -1,139 +0,0 @@ -. - -/** - * Table Definition for request_queue - */ - -defined('GNUSOCIAL') || die(); - -class Group_join_queue extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'group_join_queue'; // table name - public $profile_id; - public $group_id; - public $created; - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'description' => 'Holder for group join requests awaiting moderation.', - 'fields' => array( - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'), - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local group to join, if any'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - ), - 'primary key' => array('profile_id', 'group_id'), - 'indexes' => array( - 'group_join_queue_profile_id_created_idx' => array('profile_id', 'created'), - 'group_join_queue_group_id_created_idx' => array('group_id', 'created'), - ), - 'foreign keys' => array( - 'group_join_queue_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - 'group_join_queue_group_id_fkey' => array('user_group', array('group_id' => 'id')), - ) - ); - } - - public static function saveNew(Profile $profile, User_group $group) - { - $rq = new Group_join_queue(); - $rq->profile_id = $profile->id; - $rq->group_id = $group->id; - $rq->created = common_sql_now(); - $rq->insert(); - return $rq; - } - - public function getMember() - { - $member = Profile::getKV('id', $this->profile_id); - - if (empty($member)) { - // TRANS: Exception thrown providing an invalid profile ID. - // TRANS: %s is the invalid profile ID. - throw new Exception(sprintf(_('Profile ID %s is invalid.'), $this->profile_id)); - } - - return $member; - } - - public function getGroup() - { - $group = User_group::getKV('id', $this->group_id); - - if (empty($group)) { - // TRANS: Exception thrown providing an invalid group ID. - // TRANS: %s is the invalid group ID. - throw new Exception(sprintf(_('Group ID %s is invalid.'), $this->group_id)); - } - - return $group; - } - - /** - * Abort the pending group join... - */ - public function abort() - { - $profile = $this->getMember(); - $group = $this->getGroup(); - - if (Event::handle('StartCancelJoinGroup', array($profile, $group))) { - $this->delete(); - Event::handle('EndCancelJoinGroup', array($profile, $group)); - } - } - - /** - * Complete a pending group join... - * - * @return Group_member object on success - */ - public function complete() - { - $join = null; - $profile = $this->getMember(); - $group = $this->getGroup(); - if (Event::handle('StartJoinGroup', array($profile, $group))) { - $join = Group_member::join($group->id, $profile->id); - $this->delete(); - Event::handle('EndJoinGroup', array($profile, $group)); - } - if (!$join) { - throw new Exception('Internal error: group join failed.'); - } - $join->notify(); - return $join; - } - - /** - * Send notifications via email etc to group administrators about - * this exciting new pending moderation queue item! - */ - public function notify() - { - $joiner = Profile::getKV('id', $this->profile_id); - $group = User_group::getKV('id', $this->group_id); - mail_notify_group_join_pending($group, $joiner); - } -} diff --git a/src/Entity/Group_member.php b/src/Entity/Group_member.php deleted file mode 100644 index 5816ed0850..0000000000 --- a/src/Entity/Group_member.php +++ /dev/null @@ -1,232 +0,0 @@ -. - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for group_member - */ - -class Group_member extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'group_member'; // table name - public $group_id; // int(4) primary_key not_null - public $profile_id; // int(4) primary_key not_null - public $is_admin; // bool default_false - public $uri; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), - 'is_admin' => array('type' => 'bool', 'default' => false, 'description' => 'is this user an admin?'), - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('group_id', 'profile_id'), - 'unique keys' => array( - 'group_member_uri_key' => array('uri'), - ), - 'foreign keys' => array( - 'group_member_group_id_fkey' => array('user_group', array('group_id' => 'id')), - 'group_member_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - 'indexes' => array( - 'group_member_profile_id_created_group_id_idx' => array('profile_id', 'created', 'group_id'), - 'group_member_group_id_created_profile_id_idx' => array('profile_id', 'created', 'group_id'), - ), - ); - } - - /** - * Method to add a user to a group. - * In most cases, you should call Profile->joinGroup() instead. - * - * @param integer $group_id Group to add to - * @param integer $profile_id Profile being added - * - * @return Group_member new membership object - */ - - public static function join($group_id, $profile_id) - { - $member = new Group_member(); - - $member->group_id = $group_id; - $member->profile_id = $profile_id; - $member->created = common_sql_now(); - $member->uri = self::newUri( - Profile::getByID($profile_id), - User_group::getByID($group_id), - $member->created - ); - - $result = $member->insert(); - - if (!$result) { - common_log_db_error($member, 'INSERT', __FILE__); - // TRANS: Exception thrown when joining a group fails. - throw new Exception(_("Group join failed.")); - } - - return $member; - } - - public static function leave($group_id, $profile_id) - { - $member = Group_member::pkeyGet(array('group_id' => $group_id, - 'profile_id' => $profile_id)); - - if (empty($member)) { - // TRANS: Exception thrown when trying to leave a group the user is not a member of. - throw new Exception(_("Not part of group.")); - } - - $result = $member->delete(); - - if (!$result) { - common_log_db_error($member, 'INSERT', __FILE__); - // TRANS: Exception thrown when trying to leave a group fails. - throw new Exception(_("Group leave failed.")); - } - - return true; - } - - public function getMember() - { - $member = Profile::getKV('id', $this->profile_id); - - if (empty($member)) { - // TRANS: Exception thrown providing an invalid profile ID. - // TRANS: %s is the invalid profile ID. - throw new Exception(sprintf(_("Profile ID %s is invalid."), $this->profile_id)); - } - - return $member; - } - - public function getGroup() - { - $group = User_group::getKV('id', $this->group_id); - - if (empty($group)) { - // TRANS: Exception thrown providing an invalid group ID. - // TRANS: %s is the invalid group ID. - throw new Exception(sprintf(_('Group ID %s is invalid.'), $this->group_id)); - } - - return $group; - } - - /** - * Get stream of memberships by member - * - * @param integer $memberId profile ID of the member to fetch for - * @param integer $offset offset from start of stream to get - * @param integer $limit number of memberships to get - * - * @return Group_member stream of memberships, use fetch() to iterate - */ - - public static function byMember($memberId, $offset = 0, $limit = GROUPS_PER_PAGE) - { - $membership = new Group_member(); - - $membership->profile_id = $memberId; - - $membership->orderBy('created DESC, group_id DESC'); - - $membership->limit($offset, $limit); - - $membership->find(); - - return $membership; - } - - public function asActivity() - { - $member = $this->getMember(); - - if (!$member) { - throw new Exception("No such member: " . $this->profile_id); - } - - $group = $this->getGroup(); - - if (!$group) { - throw new Exception("No such group: " . $this->group_id); - } - - $act = new Activity(); - - $act->id = $this->getUri(); - - $act->actor = $member->asActivityObject(); - $act->verb = ActivityVerb::JOIN; - $act->objects[] = ActivityObject::fromGroup($group); - - $act->time = strtotime($this->created); - // TRANS: Activity title. - $act->title = _("Join"); - - // TRANS: Success message for subscribe to group attempt through OStatus. - // TRANS: %1$s is the member name, %2$s is the subscribed group's name. - $act->content = sprintf( - _('%1$s has joined group %2$s.'), - $member->getBestName(), - $group->getBestName() - ); - - $url = common_local_url( - 'AtomPubShowMembership', - [ - 'profile' => $member->id, - 'group' => $group->id, - ] - ); - - $act->selfLink = $url; - $act->editLink = $url; - - return $act; - } - - /** - * Send notifications via email etc to group administrators about - * this exciting new membership! - */ - public function notify() - { - mail_notify_group_join($this->getGroup(), $this->getMember()); - } - - public function getUri() - { - return $this->uri ?: self::newUri($this->getMember(), $this->getGroup()->getProfile(), $this->created); - } -} diff --git a/src/Entity/Invitation.php b/src/Entity/Invitation.php index 2f992f3b3d..6765028ea1 100644 --- a/src/Entity/Invitation.php +++ b/src/Entity/Invitation.php @@ -1,70 +1,66 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for invitation + * Entity for user invitations + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Invitation extends Managed_DataObject +class Invitation { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'invitation'; // table name - public $code; // varchar(32) primary_key not_null - public $user_id; // int(4) not_null - public $address; // varchar(191) multiple_key not_null not 255 because utf8mb4 takes more space - public $address_type; // varchar(8) multiple_key not_null - public $registered_user_id; // int(4) not_null - public $created; // datetime() + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public function convert($user) + public static function schemaDef(): array { - $orig = clone($this); - $this->registered_user_id = $user->id; - return $this->update($orig); - } - - public static function schemaDef() - { - return array( - - 'fields' => array( - 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'random code for an invitation'), - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'who sent the invitation'), - 'address' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'invitation sent to'), - 'address_type' => array('type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'registered_user_id' => array('type' => 'int', 'not null' => false, 'description' => 'if the invitation is converted, who the new user is'), - ), - 'primary key' => array('code'), - 'foreign keys' => array( - 'invitation_user_id_fkey' => array('user', array('user_id' => 'id')), - 'invitation_registered_user_id_fkey' => array('user', array('registered_user_id' => 'id')), - ), - 'indexes' => array( - 'invitation_address_address_type_idx' => array('address', 'address_type'), - 'invitation_user_id_idx' => array('user_id'), - 'invitation_registered_user_id_idx' => array('registered_user_id'), - ), - ); + return [ + 'name' => 'invitation', + 'fields' => [ + 'code' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'random code for an invitation'], + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'who sent the invitation'], + 'address' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'invitation sent to'], + 'address_type' => ['type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'registered_user_id' => ['type' => 'int', 'not null' => false, 'description' => 'if the invitation is converted, who the new user is'], + ], + 'primary key' => ['code'], + 'foreign keys' => [ + 'invitation_user_id_fkey' => ['user', ['user_id' => 'id']], + 'invitation_registered_user_id_fkey' => ['user', ['registered_user_id' => 'id']], + ], + 'indexes' => [ + 'invitation_address_idx' => ['address', 'address_type'], + 'invitation_user_id_idx' => ['user_id'], + 'invitation_registered_user_id_idx' => ['registered_user_id'], + ], + ]; } } diff --git a/src/Entity/LocalGroup.php b/src/Entity/LocalGroup.php new file mode 100644 index 0000000000..6825483b5a --- /dev/null +++ b/src/Entity/LocalGroup.php @@ -0,0 +1,62 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for local groups + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class LocalGroup +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'local_group', + 'description' => 'Record for a user group on the local site, with some additional info not in user_group', + 'fields' => [ + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'group represented'], + 'nickname' => ['type' => 'varchar', 'length' => 64, 'description' => 'group represented'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['group_id'], + 'foreign keys' => [ + 'local_group_group_id_fkey' => ['user_group', ['group_id' => 'id']], + ], + 'unique keys' => [ + 'local_group_nickname_key' => ['nickname'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Local_group.php b/src/Entity/Local_group.php deleted file mode 100644 index 2a1ff71dfc..0000000000 --- a/src/Entity/Local_group.php +++ /dev/null @@ -1,100 +0,0 @@ -. - -/** - * Table Definition for local_group - */ - -defined('GNUSOCIAL') || die(); - -class Local_group extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'local_group'; // table name - public $group_id; // int(4) primary_key not_null - public $nickname; // varchar(64) unique_key - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'description' => 'Record for a user group on the local site, with some additional info not in user_group', - 'fields' => array( - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'group represented'), - 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'group represented'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('group_id'), - 'foreign keys' => array( - 'local_group_group_id_fkey' => array('user_group', array('group_id' => 'id')), - ), - 'unique keys' => array( - 'local_group_nickname_key' => array('nickname'), - ), - ); - } - - public function getProfile() - { - return $this->getGroup()->getProfile(); - } - - public function getGroup() - { - $group = new User_group(); - $group->id = $this->group_id; - $group->find(true); - if (!$group instanceof User_group) { - common_log(LOG_ERR, 'User_group does not exist for Local_group: '.$this->group_id); - throw new NoSuchGroupException(array('id' => $this->group_id)); - } - return $group; - } - - public function setNickname($nickname) - { - $this->decache(); - $modified = common_sql_now(); - $result = $this->query(sprintf( - <<<'END' - UPDATE local_group SET nickname = %1$s, modified = %2$s - WHERE group_id = %3$d; - END, - $this->_quote($nickname), - $this->_quote($modified), - $this->group_id - )); - - if ($result) { - $this->nickname = $nickname; - $this->modified = $modified; - $this->encache(); - } else { - common_log_db_error($local, 'UPDATE', __FILE__); - // TRANS: Server exception thrown when updating a local group fails. - throw new ServerException(_('Could not update local group.')); - } - - return $result; - } -} diff --git a/src/Entity/LocationNamespace.php b/src/Entity/LocationNamespace.php new file mode 100644 index 0000000000..34cc7eb70f --- /dev/null +++ b/src/Entity/LocationNamespace.php @@ -0,0 +1,55 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for location namespace + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class LocationNamespace +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'location_namespace', + 'fields' => [ + 'id' => ['type' => 'int', 'not null' => true, 'description' => 'identity for this namespace'], + 'description' => ['type' => 'varchar', 'length' => 191, 'description' => 'description of the namespace'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date the record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Location_namespace.php b/src/Entity/Location_namespace.php deleted file mode 100644 index 0004bc630c..0000000000 --- a/src/Entity/Location_namespace.php +++ /dev/null @@ -1,52 +0,0 @@ -. - -/* - * Table Definition for location_namespace - * - * @copyright 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Location_namespace extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'location_namespace'; // table name - public $id; // int(4) primary_key not_null - public $description; // varchar(191) - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity for this namespace'), - 'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description of the namespace'), - 'created' => array('type' => 'datetime', 'description' => 'date the record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - ); - } -} diff --git a/src/Entity/LoginToken.php b/src/Entity/LoginToken.php new file mode 100644 index 0000000000..6cf8ae23e9 --- /dev/null +++ b/src/Entity/LoginToken.php @@ -0,0 +1,58 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Login tokens + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class LoginToken +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'login_token', + 'fields' => [ + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'user owning this token'], + 'token' => ['type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'token useable for logging in'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['user_id'], + 'foreign keys' => [ + 'login_token_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Login_token.php b/src/Entity/Login_token.php deleted file mode 100644 index 9e3ace1633..0000000000 --- a/src/Entity/Login_token.php +++ /dev/null @@ -1,86 +0,0 @@ -. - -/** - * Table Definition for login_token - * - * @copyright 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Login_token extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'login_token'; // table name - public $user_id; // int(4) primary_key not_null - public $token; // char(32) not_null - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user owning this token'), - 'token' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'token useable for logging in'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('user_id'), - 'foreign keys' => array( - 'login_token_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - ); - } - - const TIMEOUT = 120; // seconds after which to timeout the token - - public function makeNew($user) - { - $login_token = Login_token::getKV('user_id', $user->id); - - if (!empty($login_token)) { - $login_token->delete(); - } - - $login_token = new Login_token(); - - $login_token->user_id = $user->id; - $login_token->token = common_random_hexstr(16); - $login_token->created = common_sql_now(); - - $result = $login_token->insert(); - - if (!$result) { - common_log_db_error($login_token, 'INSERT', __FILE__); - // TRANS: Exception thrown when trying creating a login token failed. - // TRANS: %s is the user nickname for which token creation failed. - throw new Exception(sprintf( - _('Could not create login token for %s'), - $user->nickname - )); - } - - return $login_token; - } -} diff --git a/src/Entity/Managed_DataObject.php b/src/Entity/Managed_DataObject.php deleted file mode 100644 index 3aead06a57..0000000000 --- a/src/Entity/Managed_DataObject.php +++ /dev/null @@ -1,715 +0,0 @@ -. - -/** - * Wrapper for Memcached_DataObject which knows its own schema definition. - * Builds its own damn settings from a schema definition. - * - * @package GNUsocial - * @author Brion Vibber - * @copyright 2010 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -abstract class Managed_DataObject extends Memcached_DataObject -{ - /** - * The One True Thingy that must be defined and declared. - */ - public static function schemaDef() - { - throw new MethodNotImplementedException(__METHOD__); - } - - /** - * Get an instance by key - * - * @param string $k Key to use to lookup (usually 'id' for this class) - * @param mixed $v Value to lookup - * - * @return get_called_class() object if found, or null for no hits - * - */ - public static function getKV($k, $v = null) - { - return parent::getClassKV(get_called_class(), $k, $v); - } - - /** - * Get an instance by compound key - * - * This is a utility method to get a single instance with a given set of - * key-value pairs. Usually used for the primary key for a compound key; thus - * the name. - * - * @param array $kv array of key-value mappings - * - * @return get_called_class() object if found, or null for no hits - * - */ - public static function pkeyGet(array $kv) - { - return parent::pkeyGetClass(get_called_class(), $kv); - } - - public static function pkeyCols() - { - return parent::pkeyColsClass(get_called_class()); - } - - /** - * Get multiple items from the database by key - * - * @param string $keyCol name of column for key - * @param array $keyVals key values to fetch - * @param bool $skipNulls return only non-null results - * @param bool $preserve return the same tuples as input - * @return object An object with tuples to be fetched, in order - */ - public static function multiGet( - string $keyCol, - array $keyVals, - bool $skipNulls = true, - bool $preserve = false - ): object { - return parent::multiGetClass( - get_called_class(), - $keyCol, - $keyVals, - $skipNulls, - $preserve - ); - } - - /** - * Get multiple items from the database by key - * - * @param string $keyCol name of column for key - * @param array $keyVals key values to fetch - * @param array $otherCols Other columns to hold fixed - * - * @return array Array mapping $keyVals to objects, or null if not found - */ - public static function pivotGet($keyCol, array $keyVals, array $otherCols = []) - { - return parent::pivotGetClass(get_called_class(), $keyCol, $keyVals, $otherCols); - } - - /** - * Get a multi-instance object - * - * This is a utility method to get multiple instances with a given set of - * values for a specific column. - * - * @param string $keyCol key column name - * @param array $keyVals array of key values - * - * @return get_called_class() object with multiple instances if found, - * Exception is thrown when no entries are found. - * - */ - public static function listFind($keyCol, array $keyVals) - { - return parent::listFindClass(get_called_class(), $keyCol, $keyVals); - } - - /** - * Get a multi-instance object separated into an array - * - * This is a utility method to get multiple instances with a given set of - * values for a specific key column. Usually used for the primary key when - * multiple values are desired. Result is an array. - * - * @param string $keyCol key column name - * @param array $keyVals array of key values - * - * @return array with an get_called_class() object for each $keyVals entry - * - */ - public static function listGet($keyCol, array $keyVals) - { - return parent::listGetClass(get_called_class(), $keyCol, $keyVals); - } - - /** - * get/set an associative array of table columns - * - * @access public - * @return array (associative) - */ - public function table() - { - $table = static::schemaDef(); - return array_map(array($this, 'columnBitmap'), $table['fields']); - } - - /** - * get/set an array of table primary keys - * - * Key info is pulled from the table definition array. - * - * @access private - * @return array - */ - public function keys() - { - return array_keys($this->keyTypes()); - } - - /** - * Get a sequence key - * - * Returns the first serial column defined in the table, if any. - * - * @access private - * @return array (column,use_native,sequence_name) - */ - - public function sequenceKey() - { - $table = static::schemaDef(); - foreach ($table['fields'] as $name => $column) { - if ($column['type'] == 'serial') { - // We have a serial/autoincrement column. - // Declare it to be a native sequence! - return array($name, true, false); - } - } - - // No sequence key on this table. - return array(false, false, false); - } - - /** - * Return key definitions for DB_DataObject and Memcache_DataObject. - * - * DB_DataObject needs to know about keys that the table has; this function - * defines them. - * - * @return array key definitions - */ - - public function keyTypes() - { - $table = static::schemaDef(); - $keys = array(); - - if (!empty($table['unique keys'])) { - foreach ($table['unique keys'] as $idx => $fields) { - foreach ($fields as $name) { - $keys[$name] = 'U'; - } - } - } - - if (!empty($table['primary key'])) { - foreach ($table['primary key'] as $name) { - $keys[$name] = 'K'; - } - } - return $keys; - } - - /** - * Build the appropriate DB_DataObject bitfield map for this field. - * - * @param array $column - * @return int - */ - public function columnBitmap($column) - { - $type = $column['type']; - - // For quoting style... - $intTypes = [ - 'int', - 'float', - 'serial', - 'numeric' - ]; - if (in_array($type, $intTypes)) { - $style = DB_DATAOBJECT_INT; - } else { - $style = DB_DATAOBJECT_STR; - } - - // Data type formatting style... - $formatStyles = [ - 'blob' => DB_DATAOBJECT_BLOB, - 'text' => DB_DATAOBJECT_TXT, - 'bool' => DB_DATAOBJECT_BOOL, - 'date' => DB_DATAOBJECT_DATE, - 'time' => DB_DATAOBJECT_TIME, - 'datetime' => DB_DATAOBJECT_DATE | DB_DATAOBJECT_TIME, - ]; - - if (isset($formatStyles[$type])) { - $style |= $formatStyles[$type]; - } - - // Nullable? - if (!empty($column['not null'])) { - $style |= DB_DATAOBJECT_NOTNULL; - } - - return $style; - } - - public function links() - { - $links = array(); - - $table = static::schemaDef(); - - foreach ($table['foreign keys'] as $keyname => $keydef) { - if (count($keydef) == 2 && is_string($keydef[0]) && is_array($keydef[1]) && count($keydef[1]) == 1) { - if (isset($keydef[1][0])) { - $links[$keydef[1][0]] = $keydef[0].':'.$keydef[1][1]; - } - } - } - return $links; - } - - /** - * Return a list of all primary/unique keys / vals that will be used for - * caching. This will understand compound unique keys, which - * Memcached_DataObject doesn't have enough info to handle properly. - * - * @return array of strings - * @throws MethodNotImplementedException - * @throws ServerException - */ - public function _allCacheKeys() - { - $table = static::schemaDef(); - $ckeys = array(); - - if (!empty($table['unique keys'])) { - $keyNames = $table['unique keys']; - foreach ($keyNames as $idx => $fields) { - $val = array(); - foreach ($fields as $name) { - $val[$name] = self::valueString($this->$name); - } - $ckeys[] = self::multicacheKey($this->tableName(), $val); - } - } - - if (!empty($table['primary key'])) { - $fields = $table['primary key']; - $val = array(); - foreach ($fields as $name) { - $val[$name] = self::valueString($this->$name); - } - $ckeys[] = self::multicacheKey($this->tableName(), $val); - } - return $ckeys; - } - - /** - * Returns an object by looking at the primary key column(s). - * - * Will require all primary key columns to be defined in an associative array - * and ignore any keys which are not part of the primary key. - * - * Will NOT accept NULL values as part of primary key. - * - * @param array $vals Must match all primary key columns for the dataobject. - * - * @return Managed_DataObject of the get_called_class() type - * @throws NoResultException if no object with that primary key - */ - public static function getByPK(array $vals) - { - $classname = get_called_class(); - - $pkey = static::pkeyCols(); - if (is_null($pkey)) { - throw new ServerException("Failed to get primary key columns for class '{$classname}'"); - } - - $object = new $classname(); - foreach ($pkey as $col) { - if (!array_key_exists($col, $vals)) { - throw new ServerException("Missing primary key column '{$col}' for ".get_called_class()." among provided keys: ".implode(',', array_keys($vals))); - } elseif (is_null($vals[$col])) { - throw new ServerException("NULL values not allowed in getByPK for column '{$col}'"); - } - $object->$col = $vals[$col]; - } - if (!$object->find(true)) { - throw new NoResultException($object); - } - return $object; - } - - /** - * Returns an object by looking at given unique key columns. - * - * Will NOT accept NULL values for a unique key column. Ignores non-key values. - * - * @param array $vals All array keys which are set must be non-null. - * - * @return Managed_DataObject of the get_called_class() type - * @throws NoResultException if no object with that primary key - */ - public static function getByKeys(array $vals) - { - $classname = get_called_class(); - - $object = new $classname(); - - $keys = $object->keys(); - if (is_null($keys)) { - throw new ServerException("Failed to get key columns for class '{$classname}'"); - } - - foreach ($keys as $col) { - if (!array_key_exists($col, $vals)) { - continue; - } elseif (is_null($vals[$col])) { - throw new ServerException("NULL values not allowed in getByKeys for column '{$col}'"); - } - $object->$col = $vals[$col]; - } - if (!$object->find(true)) { - throw new NoResultException($object); - } - return $object; - } - - public static function getByID($id) - { - if (!property_exists(get_called_class(), 'id')) { - throw new ServerException('Trying to get undefined property of dataobject class.'); - } - if (empty($id)) { - throw new EmptyPkeyValueException(get_called_class(), 'id'); - } - // getByPK throws exception if id is null - // or if the class does not have a single 'id' column as primary key - return static::getByPK(array('id' => $id)); - } - - public static function getByUri($uri) - { - if (!property_exists(get_called_class(), 'uri')) { - throw new ServerException('Trying to get undefined property of dataobject class.'); - } - if (empty($uri)) { - throw new EmptyPkeyValueException(get_called_class(), 'uri'); - } - - $class = get_called_class(); - $obj = new $class(); - $obj->uri = $uri; - if (!$obj->find(true)) { - throw new NoResultException($obj); - } - return $obj; - } - - /** - * Returns an ID, checked that it is set and reasonably valid - * - * If this dataobject uses a special id field (not 'id'), just - * implement your ID getting method in the child class. - * - * @return int ID of dataobject - * @throws Exception (when ID is not available or not set yet) - */ - public function getID() - { - // FIXME: Make these exceptions more specific (their own classes) - if (!isset($this->id)) { - throw new Exception('No ID set.'); - } elseif (empty($this->id)) { - throw new Exception('Empty ID for object! (not inserted yet?).'); - } - - return intval($this->id); - } - - /** - * Check whether the column is NULL in SQL - * - * @param string $key column property name - * - * @return bool - */ - public function isNull(string $key): bool - { - if (array_key_exists($key, get_object_vars($this)) - && is_null($this->$key)) { - // If there was no fetch, this is a false positive. - return true; - } elseif (is_object($this->$key) - && $this->$key instanceof DB_DataObject_Cast - && $this->$key->type === 'sql') { - // This is cast to raw SQL, let's see if it's NULL. - return (strcasecmp($this->$key->value, 'NULL') == 0); - } elseif (DB_DataObject::_is_null($this, $key)) { - // DataObject's NULL magic should be disabled, - // this is just for completeness. - return true; - } - return false; - } - - /** - * WARNING: Only use this on Profile and Notice. We should probably do - * this with traits/"implements" or whatever, but that's over the top - * right now, I'm just throwing this in here to avoid code duplication - * in Profile and Notice classes. - */ - public function getAliases() - { - return array_keys($this->getAliasesWithIDs()); - } - - public function getAliasesWithIDs() - { - $aliases = array(); - $aliases[$this->getUri()] = $this->getID(); - - try { - $aliases[$this->getUrl()] = $this->getID(); - } catch (InvalidUrlException $e) { - // getUrl failed because no valid URL could be returned, just ignore it - } - - if (common_config('fix', 'fancyurls')) { - /** - * Here we add some hacky hotfixes for remote lookups that have been taught the - * (at least now) wrong URI but it's still obviously the same user. Such as: - * - https://site.example/user/1 even if the client requests https://site.example/index.php/user/1 - * - https://site.example/user/1 even if the client requests https://site.example//index.php/user/1 - * - https://site.example/index.php/user/1 even if the client requests https://site.example/user/1 - * - https://site.example/index.php/user/1 even if the client requests https://site.example///index.php/user/1 - */ - foreach ($aliases as $alias=>$id) { - try { - // get a "fancy url" version of the alias, even without index.php/ - $alt_url = common_fake_local_fancy_url($alias); - // store this as well so remote sites can be sure we really are the same profile - $aliases[$alt_url] = $id; - } catch (Exception $e) { - // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be - } - - try { - // get a non-"fancy url" version of the alias, i.e. add index.php/ - $alt_url = common_fake_local_nonfancy_url($alias); - // store this as well so remote sites can be sure we really are the same profile - $aliases[$alt_url] = $id; - } catch (Exception $e) { - // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be - } - } - } - return $aliases; - } - - /** - * Set the attribute defined as "timestamp" to CURRENT_TIMESTAMP. - * This is hooked in update() and updateWithKeys() to update "modified". - * - * @access private - * @return void - */ - private function updateAutoTimestamps(): void - { - $table = static::schemaDef(); - foreach ($table['fields'] as $name => $col) { - if ($col['type'] === 'timestamp' - && !array_key_exists('default', $col) - && !isset($this->$name)) { - $this->$name = common_sql_now(); - } - } - } - - /** - * update() won't write key columns, so we have to do it ourselves. - * This also automatically calls "update" _before_ it sets the keys. - * FIXME: This only works with single-column primary keys so far! Beware! - * - * @param Managed_DataObject $orig Must be "instanceof" $this - * @param string $pid Primary ID column (no escaping is done on column name!) - * @return bool|void - * @throws MethodNotImplementedException - * @throws ServerException - */ - public function updateWithKeys(Managed_DataObject $orig, ?string $pid = null) - { - if (!$orig instanceof $this) { - throw new ServerException('Tried updating a DataObject with a different class than itself.'); - } - - if ($this->N <1) { - throw new ServerException('DataObject must be the result of a query (N>=1) before updateWithKeys()'); - } - - $this->onUpdateKeys($orig); - - // do it in a transaction - $this->query('START TRANSACTION'); - - // ON UPDATE CURRENT_TIMESTAMP behaviour - // @fixme Should the value be reverted back if transaction failed? - $this->updateAutoTimestamps(); - - $parts = []; - foreach ($this->keys() as $k) { - $v = $this->table()[$k]; - if ($this->$k !== $orig->$k) { - if (is_object($this->$k) && $this->$k instanceof DB_DataObject_Cast) { - $value = $this->$k->toString($v, $this->getDatabaseConnection()); - } elseif (DB_DataObject::_is_null($this, $k)) { - $value = 'NULL'; - } elseif ($v & DB_DATAOBJECT_STR) { // if a string - $value = $this->_quote((string) $this->$k); - } else { - $value = (int) $this->$k; - } - $parts[] = "{$k} = {$value}"; - } - } - if (count($parts) == 0) { - // No changes to keys, it's safe to run ->update(...) - if ($this->update($orig) === false) { - common_log_db_error($this, 'UPDATE', __FILE__); - // rollback as something bad occurred - $this->query('ROLLBACK'); - throw new ServerException("Could not UPDATE non-keys for {$this->tableName()}"); - } - $orig->decache(); - $this->encache(); - - // commit our db transaction since we won't reach the COMMIT below - $this->query('COMMIT'); - // @FIXME return true only if something changed (otherwise 0) - return true; - } - - if ($pid === null) { - $schema = static::schemaDef(); - $pid = $schema['primary key']; - unset($schema); - } - $pidWhere = []; - foreach ((array) $pid as $pidCol) { - $pidWhere[] = sprintf('%1$s = %2$s', $pidCol, $this->_quote($orig->$pidCol)); - } - if (empty($pidWhere)) { - throw new ServerException('No primary ID column(s) set for updateWithKeys'); - } - - $qry = sprintf( - 'UPDATE %1$s SET %2$s WHERE %3$s', - $this->escapedTableName(), - implode(', ', $parts), - implode(' AND ', $pidWhere) - ); - - $result = $this->query($qry); - if ($result === false) { - common_log_db_error($this, 'UPDATE', __FILE__); - // rollback as something bad occurred - $this->query('ROLLBACK'); - throw new ServerException("Could not UPDATE key fields for {$this->tableName()}"); - } - - // Update non-keys too, if the previous endeavour worked. - // The ->update call uses "$this" values for keys, that's why we can't do this until - // the keys are updated (because they might differ from $orig and update the wrong entries). - if ($this->update($orig) === false) { - common_log_db_error($this, 'UPDATE', __FILE__); - // rollback as something bad occurred - $this->query('ROLLBACK'); - throw new ServerException("Could not UPDATE non-keys for {$this->tableName()}"); - } - $orig->decache(); - $this->encache(); - - // commit our db transaction - $this->query('COMMIT'); - // @FIXME return true only if something changed (otherwise 0) - return $result; - } - - public static function beforeSchemaUpdate() - { - // NOOP - } - - public static function newUri(Profile $actor, Managed_DataObject $object, $created = null) - { - if (is_null($created)) { - $created = common_sql_now(); - } - return TagURI::mint( - strtolower(get_called_class()) . ':%d:%s:%d:%s', - $actor->getID(), - ActivityUtils::resolveUri($object->getObjectType(), true), - $object->getID(), - common_date_iso8601($created) - ); - } - - protected function onInsert() - { - // NOOP by default - } - - protected function onUpdate($dataObject=false) - { - // NOOP by default - } - - protected function onUpdateKeys(Managed_DataObject $orig) - { - // NOOP by default - } - - public function insert() - { - $this->onInsert(); - $result = parent::insert(); - - // Make this object aware of the changed "modified" attribute. - // Sets it approximately to the same value as DEFAULT CURRENT_TIMESTAMP - // just did (@fixme). - if ($result) { - $this->updateAutoTimestamps(); - } - return $result; - } - - public function update($dataObject = false) - { - $this->onUpdate($dataObject); - - // ON UPDATE CURRENT_TIMESTAMP behaviour - // @fixme Should the value be reverted back if transaction failed? - $this->updateAutoTimestamps(); - - return parent::update($dataObject); - } -} diff --git a/src/Entity/Memcached_DataObject.php b/src/Entity/Memcached_DataObject.php deleted file mode 100644 index d2140e3497..0000000000 --- a/src/Entity/Memcached_DataObject.php +++ /dev/null @@ -1,1058 +0,0 @@ -. - -/** - * @copyright 2008, 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Memcached_DataObject extends Safe_DataObject -{ - /** - * Wrapper for DB_DataObject's static lookup using memcached - * as backing instead of an in-process cache array. - * - * @param string $cls classname of object type to load - * @param mixed $k key field name, or value for primary key - * @param mixed $v key field value, or leave out for primary key lookup - * @return mixed Memcached_DataObject subtype or false - */ - public static function getClassKV($cls, $k, $v = null) - { - if (is_null($v)) { - $v = $k; - $keys = static::pkeyCols(); - if (count($keys) > 1) { - // FIXME: maybe call pkeyGetClass() ourselves? - throw new Exception('Use pkeyGetClass() for compound primary keys'); - } - $k = $keys[0]; - } - $i = self::getcached($cls, $k, $v); - if ($i === false) { // false == cache miss - $i = new $cls; - $result = $i->get($k, $v); - if ($result) { - // Hit! - $i->encache(); - } else { - // save the fact that no such row exists - $c = self::memcache(); - if (!empty($c)) { - $ck = self::cachekey($cls, $k, $v); - $c->set($ck, null); - } - $i = false; - } - } - return $i; - } - - /** - * Get multiple items from the database by key - * - * @param string $cls Class to fetch - * @param string $keyCol name of column for key - * @param array $keyVals key values to fetch - * @param bool $skipNulls return only non-null results - * @param bool $preserve return the same tuples as input - * @return object An object with tuples to be fetched, in order - */ - public static function multiGetClass( - string $cls, - string $keyCol, - array $keyVals, - bool $skipNulls, - bool $preserve - ): object { - $obj = new $cls(); - - // Do not select anything extra - $obj->selectAdd(); - $obj->selectAdd($obj->escapedTableName() . '.*'); - - // A PHP-compatible datatype to check against - $col_type = $obj->columnType($keyCol); - - // The code below assumes one of the two results - if (!in_array($col_type, ['int', 'string'])) { - throw new ServerException( - 'Cannot do multiGet on anything but integer or string columns' - ); - } - - // Actually need to know if MariaDB or Oracle MySQL this time - $db_type = common_config('db', 'type'); - if ($db_type === 'mysql') { - $tmp_obj = new $cls(); - $tmp_obj->query('SELECT 0 /*M! + 1 */ AS is_mariadb;'); - if ($tmp_obj->fetch() && $tmp_obj->is_mariadb) { - $db_type = 'mariadb'; - } - } - - // Since we're inputting straight to a query: format and escape - $vals_escaped = []; - foreach (array_values($keyVals) as $i => $val) { - if (is_null($val)) { - $val_escaped = 'NULL'; - } elseif ($col_type === 'int') { - $val_escaped = (string)(int) $val; - } else { - $val_escaped = "'{$obj->escape($val)}'"; - } - if ($db_type !== 'mariadb') { - $vals_escaped[] = $val_escaped; - } else { - // A completely different approach for MariaDB (see below) - $vals_escaped[] = "({$val_escaped},{$i})"; - } - } - - // One way to guarantee that there is no name collision - $join_tablename = common_database_tablename( - $obj->tableName() . '_vals' - ); - $join_keyword = ($preserve ? 'RIGHT' : 'INNER') . ' JOIN'; - $vals_cast_type = ($col_type === 'int') ? 'INTEGER' : 'TEXT'; - - // A lot of magic to ensure we get an ordered reply with the same exact - // values as on input. - switch ($db_type) { - case 'pgsql': - // Explicit casting is done to cast empty arrays - $obj->_join = "\n" . sprintf( - <<_join = "\n" . sprintf( - <<_join = "\n" . sprintf( - <<whereAdd("{$obj->escapedTableName()}.{$keyCol} IS NOT NULL"); - } - - $obj->orderBy("{$join_tablename}.{$keyCol}_pos"); - - $obj->find(); - return $obj; - } - - /** - * Get multiple items from the database by key - * - * @param string $cls Class to fetch - * @param string $keyCol name of column for key - * @param array $keyVals key values to fetch - * @param boolean $otherCols Other columns to hold fixed - * - * @return array Array mapping $keyVals to objects, or null if not found - */ - public static function pivotGetClass( - $cls, - $keyCol, - array $keyVals, - array $otherCols = [] - ) { - if (is_array($keyCol)) { - foreach ($keyVals as $keyVal) { - if (!is_array($keyVal)) { - throw new ServerException( - 'keyVals passed to pivotGet must be an array of arrays ' - . 'if keyCol is an array' - ); - } - $result[implode(',', $keyVal)] = null; - } - } else { - $result = array_fill_keys($keyVals, null); - } - - $toFetch = array(); - - foreach ($keyVals as $keyVal) { - if (is_array($keyCol)) { - $kv = array_combine($keyCol, $keyVal); - } else { - $kv = array($keyCol => $keyVal); - } - - $kv = array_merge($otherCols, $kv); - - $i = self::multicache($cls, $kv); - - if ($i !== false) { - if (is_array($keyCol)) { - $result[implode(',', $keyVal)] = $i; - } else { - $result[$keyVal] = $i; - } - } elseif (!empty($keyVal)) { - $toFetch[] = $keyVal; - } - } - - if (count($toFetch) > 0) { - $i = new $cls; - foreach ($otherCols as $otherKeyCol => $otherKeyVal) { - $i->$otherKeyCol = $otherKeyVal; - } - if (is_array($keyCol)) { - $i->whereAdd(self::_inMultiKey($i, $keyCol, $toFetch)); - } else { - $i->whereAddIn($keyCol, $toFetch, $i->columnType($keyCol)); - } - if ($i->find()) { - while ($i->fetch()) { - $copy = clone($i); - $copy->encache(); - if (is_array($keyCol)) { - $vals = array(); - foreach ($keyCol as $k) { - $vals[] = $i->$k; - } - $result[implode(',', $vals)] = $copy; - } else { - $result[$i->$keyCol] = $copy; - } - } - } - - // Save state of DB misses - - foreach ($toFetch as $keyVal) { - $r = null; - if (is_array($keyCol)) { - $r = $result[implode(',', $keyVal)]; - } else { - $r = $result[$keyVal]; - } - if (empty($r)) { - if (is_array($keyCol)) { - $kv = array_combine($keyCol, $keyVal); - } else { - $kv = array($keyCol => $keyVal); - } - $kv = array_merge($otherCols, $kv); - // save the fact that no such row exists - $c = self::memcache(); - if (!empty($c)) { - $ck = self::multicacheKey($cls, $kv); - $c->set($ck, null); - } - } - } - } - - return $result; - } - - public static function _inMultiKey($i, $cols, $values) - { - $types = array(); - - foreach ($cols as $col) { - $types[$col] = $i->columnType($col); - } - - $first = true; - - $query = ''; - - foreach ($values as $value) { - if ($first) { - $query .= '( '; - $first = false; - } else { - $query .= ' OR '; - } - $query .= '( '; - $i = 0; - $firstc = true; - foreach ($cols as $col) { - if (!$firstc) { - $query .= ' AND '; - } else { - $firstc = false; - } - switch ($types[$col]) { - case 'string': - case 'datetime': - $query .= sprintf("%s = %s", $col, $i->_quote($value[$i])); - break; - default: - $query .= sprintf("%s = %s", $col, $value[$i]); - break; - } - } - $query .= ') '; - } - - if (!$first) { - $query .= ' )'; - } - - return $query; - } - - public static function pkeyColsClass($cls) - { - $i = new $cls; - $types = $i->keyTypes(); - ksort($types); - - $pkey = array(); - - foreach ($types as $key => $type) { - if ($type == 'K' || $type == 'N') { - $pkey[] = $key; - } - } - - return $pkey; - } - - public static function listFindClass($cls, $keyCol, array $keyVals) - { - $i = new $cls; - $i->whereAddIn($keyCol, $keyVals, $i->columnType($keyCol)); - if (!$i->find()) { - throw new NoResultException($i); - } - - return $i; - } - - public static function listGetClass($cls, $keyCol, array $keyVals) - { - $pkeyMap = array_fill_keys($keyVals, array()); - $result = array_fill_keys($keyVals, array()); - - $pkeyCols = static::pkeyCols(); - - $toFetch = array(); - $allPkeys = array(); - - // We only cache keys -- not objects! - - foreach ($keyVals as $keyVal) { - $l = self::cacheGet(sprintf('%s:list-ids:%s:%s', strtolower($cls), $keyCol, $keyVal)); - if ($l !== false) { - $pkeyMap[$keyVal] = $l; - foreach ($l as $pkey) { - $allPkeys[] = $pkey; - } - } else { - $toFetch[] = $keyVal; - } - } - - if (count($allPkeys) > 0) { - $keyResults = self::pivotGetClass($cls, $pkeyCols, $allPkeys); - - foreach ($pkeyMap as $keyVal => $pkeyList) { - foreach ($pkeyList as $pkeyVal) { - $i = $keyResults[implode(',', $pkeyVal)]; - if (!empty($i)) { - $result[$keyVal][] = $i; - } - } - } - } - - if (count($toFetch) > 0) { - try { - $i = self::listFindClass($cls, $keyCol, $toFetch); - - while ($i->fetch()) { - $copy = clone($i); - $copy->encache(); - $result[$i->$keyCol][] = $copy; - $pkeyVal = array(); - foreach ($pkeyCols as $pkeyCol) { - $pkeyVal[] = $i->$pkeyCol; - } - $pkeyMap[$i->$keyCol][] = $pkeyVal; - } - } catch (NoResultException $e) { - // no results found for our keyVals, so we leave them as empty arrays - } - foreach ($toFetch as $keyVal) { - self::cacheSet( - sprintf("%s:list-ids:%s:%s", strtolower($cls), $keyCol, $keyVal), - $pkeyMap[$keyVal] - ); - } - } - - return $result; - } - - public function escapedTableName() - { - return common_database_tablename($this->tableName()); - } - - public function columnType($columnName) - { - $keys = $this->table(); - if (!array_key_exists($columnName, $keys)) { - throw new Exception('Unknown key column ' . $columnName . ' in ' . join(',', array_keys($keys))); - } - - $def = $keys[$columnName]; - - if ($def & DB_DATAOBJECT_INT) { - return 'int'; - } else { - return 'string'; - } - } - - /** - * @todo FIXME: Should this return false on lookup fail to match getKV? - */ - public static function pkeyGetClass($cls, array $kv) - { - $i = self::multicache($cls, $kv); - if ($i !== false) { // false == cache miss - return $i; - } else { - $i = new $cls; - foreach ($kv as $k => $v) { - if (is_null($v)) { - // XXX: possible SQL injection...? Don't - // pass keys from the browser, eh. - $i->whereAdd("$k is null"); - } else { - $i->$k = $v; - } - } - if ($i->find(true)) { - $i->encache(); - } else { - $i = null; - $c = self::memcache(); - if (!empty($c)) { - $ck = self::multicacheKey($cls, $kv); - $c->set($ck, null); - } - } - return $i; - } - } - - public function insert() - { - $result = parent::insert(); - if ($result !== false) { - // In case of cached negative lookups - $this->decache(); - } - return $result; - } - - public function update($dataObject = false) - { - if (is_object($dataObject) && $dataObject instanceof Memcached_DataObject) { - $dataObject->decache(); // might be different keys - } - $result = parent::update($dataObject); - if ($result !== false) { - // Cannot encache yet, so decache instead - $this->decache(); - } - return $result; - } - - public function delete($useWhere = false) - { - $this->decache(); # while we still have the values! - return parent::delete($useWhere); - } - - public static function memcache() - { - return Cache::instance(); - } - - public static function cacheKey($cls, $k, $v) - { - if (is_object($cls) || is_object($k) || (is_object($v) && !($v instanceof DB_DataObject_Cast))) { - $e = new Exception(); - common_log(LOG_ERR, __METHOD__ . ' object in param: ' . - str_replace("\n", " ", $e->getTraceAsString())); - } - $vstr = self::valueString($v); - return Cache::key(strtolower($cls).':'.$k.':'.$vstr); - } - - public static function getcached($cls, $k, $v) - { - $c = self::memcache(); - if (!$c) { - return false; - } else { - $obj = $c->get(self::cacheKey($cls, $k, $v)); - if (0 == strcasecmp($cls, 'User')) { - // Special case for User - if (is_object($obj) && is_object($obj->id)) { - common_log(LOG_ERR, "User " . $obj->nickname . " was cached with User as ID; deleting"); - $c->delete(self::cacheKey($cls, $k, $v)); - return false; - } - } - return $obj; - } - } - - public function keyTypes() - { - // ini-based classes return number-indexed arrays. handbuilt - // classes return column => keytype. Make this uniform. - - $keys = $this->keys(); - - $keyskeys = array_keys($keys); - - if (is_string($keyskeys[0])) { - return $keys; - } - - global $_DB_DATAOBJECT; - if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"])) { - $this->databaseStructure(); - } - return $_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()."__keys"]; - } - - public function encache() - { - if ($this->N < 1) { - // Caching breaks when it is too early. - $e = new Exception(); - common_log( - LOG_ERR, - 'DataObject must be the result of a query (N>=1) before encache() ' - . str_replace("\n", ' ', $e->getTraceAsString()) - ); - return false; - } - - $c = self::memcache(); - - if (!$c) { - return false; - } elseif ($this->tableName() === 'user' && is_object($this->id)) { - // Special case for User bug - $e = new Exception(); - common_log(LOG_ERR, __METHOD__ . ' caching user with User object as ID ' . - str_replace("\n", " ", $e->getTraceAsString())); - return false; - } else { - $keys = $this->_allCacheKeys(); - - foreach ($keys as $key) { - $c->set($key, $this); - } - } - } - - public function decache() - { - $c = self::memcache(); - - if (!$c) { - return false; - } - - $keys = $this->_allCacheKeys(); - - foreach ($keys as $key) { - $c->delete($key, $this); - } - } - - public function _allCacheKeys() - { - $ckeys = array(); - - $types = $this->keyTypes(); - ksort($types); - - $pkey = array(); - $pval = array(); - - foreach ($types as $key => $type) { - assert(!empty($key)); - - if ($type == 'U') { - if (empty($this->$key)) { - continue; - } - $ckeys[] = self::cacheKey($this->tableName(), $key, self::valueString($this->$key)); - } elseif (in_array($type, ['K', 'N'])) { - $pkey[] = $key; - $pval[] = self::valueString($this->$key); - } else { - // Low level exception. No need for i18n as discussed with Brion. - throw new Exception("Unknown key type $key => $type for " . $this->tableName()); - } - } - - assert(count($pkey) > 0); - - // XXX: should work for both compound and scalar pkeys - $pvals = implode(',', $pval); - $pkeys = implode(',', $pkey); - - $ckeys[] = self::cacheKey($this->tableName(), $pkeys, $pvals); - - return $ckeys; - } - - public static function multicache($cls, array $kv) - { - ksort($kv); - $c = self::memcache(); - if (!$c) { - return false; - } else { - return $c->get(self::multicacheKey($cls, $kv)); - } - } - - public static function multicacheKey($cls, array $kv) - { - ksort($kv); - $pkeys = implode(',', array_keys($kv)); - $pvals = implode(',', array_values($kv)); - return self::cacheKey($cls, $pkeys, $pvals); - } - - public function getSearchEngine($table) - { - require_once INSTALLDIR . '/lib/search/search_engines.php'; - - if (Event::handle('GetSearchEngine', [$this, $table, &$search_engine])) { - $type = common_config('search', 'type'); - if ($type === 'like') { - $search_engine = new SQLLikeSearch($this, $table); - } elseif ($type === 'fulltext') { - switch (common_config('db', 'type')) { - case 'pgsql': - $search_engine = new PostgreSQLSearch($this, $table); - break; - case 'mysql': - $search_engine = new MySQLSearch($this, $table); - break; - default: - throw new ServerException('Unknown DB type selected.'); - } - } else { - // Low level exception. No need for i18n as discussed with Brion. - throw new ServerException('Unknown search type: ' . $type); - } - } - - return $search_engine; - } - - public static function cachedQuery($cls, $qry, $expiry = 3600) - { - $c = self::memcache(); - if (!$c) { - $inst = new $cls(); - $inst->query($qry); - return $inst; - } - $key_part = Cache::keyize($cls).':'.md5($qry); - $ckey = Cache::key($key_part); - $stored = $c->get($ckey); - - if ($stored !== false) { - return new ArrayWrapper($stored); - } - - $inst = new $cls(); - $inst->query($qry); - $cached = array(); - while ($inst->fetch()) { - $cached[] = clone($inst); - } - $inst->free(); - $c->set($ckey, $cached, Cache::COMPRESSED, $expiry); - return new ArrayWrapper($cached); - } - - /** - * sends query to database - this is the private one that must work - * - internal functions use this rather than $this->query() - * - * Overridden to do logging. - * - * @param string $string - * @access private - * @return mixed none or PEAR_Error - */ - public function _query($string) - { - if (common_config('db', 'annotate_queries')) { - $string = $this->annotateQuery($string); - } - - $start = hrtime(true); - $fail = false; - $result = null; - if (Event::handle('StartDBQuery', array($this, $string, &$result))) { - common_perf_counter('query', $string); - try { - $result = parent::_query($string); - } catch (Exception $e) { - $fail = $e; - } - Event::handle('EndDBQuery', array($this, $string, &$result)); - } - $delta = (hrtime(true) - $start) / 1000000000; - - $limit = common_config('db', 'log_slow_queries'); - if (($limit > 0 && $delta >= $limit) || common_config('db', 'log_queries')) { - $clean = $this->sanitizeQuery($string); - if ($fail) { - $msg = sprintf("FAILED DB query (%0.3fs): %s - %s", $delta, $fail->getMessage(), $clean); - } else { - $msg = sprintf("DB query (%0.3fs): %s", $delta, $clean); - } - common_log(LOG_DEBUG, $msg); - } - - if ($fail) { - throw $fail; - } - return $result; - } - - /** - * Find the first caller in the stack trace that's not a - * low-level database function and add a comment to the - * query string. This should then be visible in process lists - * and slow query logs, to help identify problem areas. - * - * Also marks whether this was a web GET/POST or which daemon - * was running it. - * - * @param string $string SQL query string - * @return string SQL query string, with a comment in it - */ - public function annotateQuery($string) - { - $ignore = array('annotateQuery', - '_query', - 'query', - 'get', - 'insert', - 'delete', - 'update', - 'find'); - $ignoreStatic = array('getKV', - 'getClassKV', - 'pkeyGet', - 'pkeyGetClass', - 'cachedQuery'); - $here = get_class($this); // if we get confused - $bt = debug_backtrace(); - - // Find the first caller that's not us? - foreach ($bt as $frame) { - $func = $frame['function']; - if (isset($frame['type']) && $frame['type'] == '::') { - if (in_array($func, $ignoreStatic)) { - continue; - } - $here = $frame['class'] . '::' . $func; - break; - } elseif (isset($frame['type']) && $frame['type'] === '->') { - if ($frame['object'] === $this && in_array($func, $ignore)) { - continue; - } - if (in_array($func, $ignoreStatic)) { - continue; // @todo FIXME: This shouldn't be needed? - } - $here = get_class($frame['object']) . '->' . $func; - break; - } - $here = $func; - break; - } - - if (php_sapi_name() == 'cli') { - $context = basename($_SERVER['PHP_SELF']); - } else { - $context = $_SERVER['REQUEST_METHOD']; - } - - // Slip the comment in after the first command, - // or DB_DataObject gets confused about handling inserts and such. - $parts = explode(' ', $string, 2); - $parts[0] .= " /* $context $here */"; - return implode(' ', $parts); - } - - // Sanitize a query for logging - // @fixme don't trim spaces in string literals - public function sanitizeQuery($string) - { - $string = preg_replace('/\s+/', ' ', $string); - $string = trim($string); - return $string; - } - - public function _connect() - { - global $_DB_DATAOBJECT, $_PEAR; - - $sum = $this->_getDbDsnMD5(); - - if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$sum]) && - !$_PEAR->isError($_DB_DATAOBJECT['CONNECTIONS'][$sum])) { - $exists = true; - } else { - $exists = false; - } - - // @fixme horrible evil hack! - // - // In multisite configuration we don't want to keep around a separate - // connection for every database; we could end up with thousands of - // connections open per thread. In an ideal world we might keep - // a connection per server and select different databases, but that'd - // be reliant on having the same db username/pass as well. - // - // MySQL connections are cheap enough we're going to try just - // closing out the old connection and reopening when we encounter - // a new DSN. - // - // WARNING WARNING if we end up actually using multiple DBs at a time - // we'll need some fancier logic here. - if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS']) && php_sapi_name() == 'cli') { - foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) { - if ($_PEAR->isError($conn)) { - common_log(LOG_WARNING, __METHOD__ . " cannot disconnect failed DB connection: '".$conn->getMessage()."'."); - } elseif (!empty($conn)) { - $conn->disconnect(); - } - unset($_DB_DATAOBJECT['CONNECTIONS'][$index]); - } - } - - $result = parent::_connect(); - - if ($result && !$exists) { - // Required to make timestamp values usefully comparable. - if (common_config('db', 'type') !== 'mysql') { - parent::_query("SET TIME ZONE INTERVAL '+00:00' HOUR TO MINUTE"); - } else { - parent::_query("SET time_zone = '+0:00'"); - } - } - - return $result; - } - - // XXX: largely cadged from DB_DataObject - - public function _getDbDsnMD5() - { - if ($this->_database_dsn_md5) { - return $this->_database_dsn_md5; - } - - $dsn = $this->_getDbDsn(); - - if (is_string($dsn)) { - $sum = md5($dsn); - } else { - /// support array based dsn's - $sum = md5(serialize($dsn)); - } - - return $sum; - } - - public function _getDbDsn() - { - global $_DB_DATAOBJECT; - - if (empty($_DB_DATAOBJECT['CONFIG'])) { - self::_loadConfig(); - } - - $options = &$_DB_DATAOBJECT['CONFIG']; - - // if the databse dsn dis defined in the object.. - - $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null; - - if (!$dsn) { - if (!$this->_database) { - $this->_database = isset($options["table_{$this->tableName()}"]) ? $options["table_{$this->tableName()}"] : null; - } - - if ($this->_database && !empty($options["database_{$this->_database}"])) { - $dsn = $options["database_{$this->_database}"]; - } elseif (!empty($options['database'])) { - $dsn = $options['database']; - } - } - - if (!$dsn) { - // TRANS: Exception thrown when database name or Data Source Name could not be found. - throw new Exception(_('No database name or DSN found anywhere.')); - } - - return $dsn; - } - - public static function blow() - { - $c = self::memcache(); - - if (empty($c)) { - return false; - } - - $args = func_get_args(); - - $format = array_shift($args); - - $keyPart = vsprintf($format, $args); - - $cacheKey = Cache::key($keyPart); - - return $c->delete($cacheKey); - } - - public function raiseError($message, $type = null, $behavior = null) - { - $id = get_class($this); - if (!empty($this->id)) { - $id .= ':' . $this->id; - } - if ($message instanceof PEAR_Error) { - $message = $message->getMessage(); - } - // Low level exception. No need for i18n as discussed with Brion. - throw new ServerException("[$id] DB_DataObject error [$type]: $message"); - } - - public static function cacheGet($keyPart) - { - $c = self::memcache(); - - if (empty($c)) { - return false; - } - - $cacheKey = Cache::key($keyPart); - - return $c->get($cacheKey); - } - - public static function cacheSet($keyPart, $value, $flag = null, $expiry = null) - { - $c = self::memcache(); - - if (empty($c)) { - return false; - } - - $cacheKey = Cache::key($keyPart); - - return $c->set($cacheKey, $value, $flag, $expiry); - } - - public static function valueString($v) - { - $vstr = null; - if (is_object($v) && $v instanceof DB_DataObject_Cast) { - switch ($v->type) { - case 'date': - $vstr = "{$v->year} - {$v->month} - {$v->day}"; - break; - case 'sql': - if (strcasecmp($v->value, 'NULL') == 0) { - // Very selectively handling NULLs. - $vstr = ''; - break; - } - // no break - case 'blob': - case 'string': - case 'datetime': - case 'time': - // Low level exception. No need for i18n as discussed with Brion. - throw new ServerException("Unhandled DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); - break; - default: - // Low level exception. No need for i18n as discussed with Brion. - throw new ServerException("Unknown DB_DataObject_Cast type passed as cacheKey value: '$v->type'"); - break; - } - } else { - $vstr = strval($v); - } - return $vstr; - } -} diff --git a/src/Entity/Nonce.php b/src/Entity/Nonce.php index 05f892b4dd..4e63b45f30 100644 --- a/src/Entity/Nonce.php +++ b/src/Entity/Nonce.php @@ -1,68 +1,58 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for nonce + * Entity for nonce + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Nonce extends Managed_DataObject +class Nonce { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'nonce'; // table name - public $consumer_key; // varchar(191) primary_key not_null not 255 because utf8mb4 takes more space - public $tok; // char(32) - public $nonce; // char(32) primary_key not_null - public $ts; // datetime() primary_key not_null - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - /** - * Compatibility hack for PHP 5.3 - * - * The statusnet.links.ini entry cannot be read because "," is no longer - * allowed in key names when read by parse_ini_file(). - * - * @return array - * @access public - */ - public function links() + public static function schemaDef(): array { - return array('consumer_key,token' => 'token:consumer_key,token'); - } - - public static function schemaDef() - { - return array( + return [ + 'name' => 'nonce', 'description' => 'OAuth nonce record', - 'fields' => array( - 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'), - 'tok' => array('type' => 'char', 'length' => 32, 'description' => 'buggy old value, ignored'), - 'nonce' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'nonce'), - 'ts' => array('type' => 'datetime', 'not null' => true, 'description' => 'timestamp sent'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('consumer_key', 'ts', 'nonce'), - ); + 'fields' => [ + 'consumer_key' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'], + 'tok' => ['type' => 'char', 'length' => 32, 'description' => 'buggy old value, ignored'], + 'nonce' => ['type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'nonce'], + 'ts' => ['type' => 'datetime', 'not null' => true, 'description' => 'timestamp sent'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['consumer_key', 'ts', 'nonce'], + ]; } } diff --git a/src/Entity/Notice.php b/src/Entity/Notice.php deleted file mode 100644 index f2c210e59a..0000000000 --- a/src/Entity/Notice.php +++ /dev/null @@ -1,3302 +0,0 @@ -. - -/** - * @category Notices - * @package GNUsocial - * @author Brenda Wallace - * @author Christopher Vollick - * @author CiaranG - * @author Craig Andrews - * @author Evan Prodromou - * @author Gina Haeussge - * @author Jeffery To - * @author Mike Cochrane - * @author Robin Millette - * @author Sarven Capadisli - * @author Tom Adams - * @author Mikael Nordfeldth - * @copyright 2009 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for notice - */ - -/* We keep 200 notices, the max number of notices available per API request, - * in the memcached cache. */ - -define('NOTICE_CACHE_WINDOW', CachingNoticeStream::CACHE_WINDOW); - -define('MAX_BOXCARS', 128); - -class Notice extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'notice'; // table name - public $id; // int(4) primary_key not_null - public $profile_id; // int(4) multiple_key not_null - public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $content; // text - public $rendered; // text - public $url; // text - public $created; // datetime() multiple_key - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $reply_to; // int(4) - public $is_local; // int(4) - public $source; // varchar(32) - public $conversation; // int(4) - public $repeat_of; // int(4) - public $verb; // varchar(191) not 255 because utf8mb4 takes more space - public $object_type; // varchar(191) not 255 because utf8mb4 takes more space - public $scope; // int(4) - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - $def = array( - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'who made the update'), - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'), - 'content' => array('type' => 'text', 'description' => 'update content', 'collate' => 'utf8_general_ci'), - 'rendered' => array('type' => 'text', 'description' => 'HTML version of the content'), - 'url' => array('type' => 'text', 'description' => 'URL of any attachment (image, video, bookmark, whatever)'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - 'reply_to' => array('type' => 'int', 'description' => 'notice replied to (usually a guess)'), - 'is_local' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'notice was generated by a user'), - 'source' => array('type' => 'varchar', 'length' => 32, 'description' => 'source of comment, like "web", "im", or "clientname"'), - 'conversation' => array('type' => 'int', 'description' => 'the local numerical conversation id'), - 'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'), - 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => null), - 'verb' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'), - 'scope' => array('type' => 'int', - 'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = groups; 8 = followers; 16 = messages; null = default'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'notice_uri_key' => array('uri'), - ), - 'foreign keys' => array( - 'notice_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - 'notice_reply_to_fkey' => array('notice', array('reply_to' => 'id')), - 'notice_conversation_fkey' => array('conversation', array('conversation' => 'id')), # note... used to refer to notice.id - 'notice_repeat_of_fkey' => array('notice', array('repeat_of' => 'id')), # @fixme: what about repeats of deleted notices? - ), - 'indexes' => array( - 'notice_is_local_created_id_idx' => array('is_local', 'created', 'id'), - 'notice_profile_id_created_id_idx' => array('profile_id', 'created', 'id'), - 'notice_profile_id_verb_scope_created_id_idx' => array('profile_id', 'verb', 'scope', 'created', 'id'), - 'notice_is_local_created_profile_id_idx' => array('is_local', 'created', 'profile_id'), - 'notice_repeat_of_created_id_idx' => array('repeat_of', 'created', 'id'), - 'notice_conversation_created_id_idx' => array('conversation', 'created', 'id'), - 'notice_object_type_idx' => array('object_type'), - 'notice_verb_idx' => array('verb'), - 'notice_url_idx' => array(array('url', 191)), // Qvitter wants this - 'notice_reply_to_idx' => array('reply_to') - ), - 'fulltext indexes' => array( - 'notice_fulltext_idx' => array('content'), - ), - ); - - return $def; - } - - /* Notice types */ - const LOCAL_PUBLIC = 1; - const REMOTE = 0; - const LOCAL_NONPUBLIC = -1; - const GATEWAY = -2; - - const PUBLIC_SCOPE = 0; // Useful fake constant - const SITE_SCOPE = 1; - const ADDRESSEE_SCOPE = 2; - const GROUP_SCOPE = 4; - const FOLLOWER_SCOPE = 8; - const MESSAGE_SCOPE = 16; - - protected $_profile = array(); - - /** - * Will always return a profile, if anything fails it will - * (through _setProfile) throw a NoProfileException. - */ - public function getProfile() - { - if (!isset($this->_profile[$this->profile_id])) { - // We could've sent getKV directly to _setProfile, but occasionally we get - // a "false" (instead of null), likely because it indicates a cache miss. - $profile = Profile::getKV('id', $this->profile_id); - $this->_setProfile($profile instanceof Profile ? $profile : null); - } - return $this->_profile[$this->profile_id]; - } - - public function _setProfile(Profile $profile=null) - { - if (!$profile instanceof Profile) { - throw new NoProfileException($this->profile_id); - } - $this->_profile[$this->profile_id] = $profile; - } - - public function deleteAs(Profile $actor, $delete_event=true) - { - if (!$this->getProfile()->sameAs($actor) && !$actor->hasRight(Right::DELETEOTHERSNOTICE)) { - throw new AuthorizationException(_('You are not allowed to delete another user\'s notice.')); - } - - $result = null; - if (!$delete_event || Event::handle('DeleteNoticeAsProfile', array($this, $actor, &$result))) { - // If $delete_event is true, we run the event. If the Event then - // returns false it is assumed everything was handled properly - // and the notice was deleted. - $result = $this->delete(); - } - return $result; - } - - protected function deleteRelated() - { - if (Event::handle('NoticeDeleteRelated', array($this))) { - // Clear related records - $this->clearReplies(); - $this->clearLocation(); - $this->clearPrefs(); - $this->clearRepeats(); - $this->clearTags(); - $this->clearGroupInboxes(); - $this->clearFiles(); - $this->clearAttentions(); - // NOTE: we don't clear queue items - } - } - - public function delete($useWhere=false) - { - $this->deleteRelated(); - - $result = parent::delete($useWhere); - - $this->blowOnDelete(); - return $result; - } - - public function getUri() - { - return $this->uri; - } - - /* - * Get a Notice object by URI. Will call external plugins for help - * using the event StartGetNoticeFromURI. - * - * @param string $uri A unique identifier for a resource (notice in this case) - */ - public static function fromUri($uri) - { - $notice = null; - - if (Event::handle('StartGetNoticeFromUri', array($uri, &$notice))) { - $notice = Notice::getKV('uri', $uri); - Event::handle('EndGetNoticeFromUri', array($uri, $notice)); - } - - if (!$notice instanceof Notice) { - throw new UnknownUriException($uri); - } - - return $notice; - } - - /** - * @param bool $anchor If false, link to just the conversation root. - * - * @return string URL to conversation - */ - public function getConversationUrl(bool $anchor = true): string - { - return Conversation::getUrlFromNotice($this, $anchor); - } - - /* - * Get the local representation URL of this notice. - */ - public function getLocalUrl() - { - return common_local_url('shownotice', array('notice' => $this->id), null, null, false); - } - - public function getTitle($imply=true) - { - $title = null; - if (Event::handle('GetNoticeTitle', array($this, &$title)) && $imply) { - // TRANS: Title of a notice posted without a title value. - // TRANS: %1$s is a user name, %2$s is the notice creation date/time. - $title = sprintf( - _('%1$s\'s status on %2$s'), - $this->getProfile()->getFancyName(), - common_exact_date($this->created) - ); - } - return $title; - } - - public function getContent() - { - return $this->content; - } - - public function getRendered() - { - // we test $this->id because if it's not inserted yet, we can't update the field - if (!empty($this->id) && (is_null($this->rendered) || $this->rendered === '')) { - // update to include rendered content on-the-fly, so we don't have to have a fix-up script in upgrade.php - common_debug('Rendering notice '.$this->getID().' as it had no rendered HTML content.'); - $orig = clone($this); - $this->rendered = common_render_content( - $this->getContent(), - $this->getProfile(), - ($this->hasParent() ? $this->getParent() : null) - ); - $this->update($orig); - } - return $this->rendered; - } - - public function getCreated() - { - return $this->created; - } - - public function getVerb($make_relative=false) - { - return ActivityUtils::resolveUri($this->verb, $make_relative); - } - - public function isVerb(array $verbs) - { - return ActivityUtils::compareVerbs($this->getVerb(), $verbs); - } - - /* - * Get the original representation URL of this notice. - * - * @param boolean $fallback Whether to fall back to generate a local URL or throw InvalidUrlException - */ - public function getUrl($fallback=false) - { - // The risk is we start having empty urls and non-http uris... - // and we can't really handle any other protocol right now. - switch (true) { - case $this->isLocal(): - return common_local_url('shownotice', array('notice' => $this->getID()), null, null, false); - case common_valid_http_url($this->url): // should we allow non-http/https URLs? - return $this->url; - case common_valid_http_url($this->uri): // Sometimes we only have the URI for remote posts. - return $this->uri; - case $fallback: - // let's generate a valid link to our locally available notice on demand - return common_local_url('shownotice', array('notice' => $this->getID()), null, null, false); - default: - throw new InvalidUrlException($this->url); - } - } - - public function getSelfLink() - { - if ($this->isLocal()) { - return common_local_url('ApiStatusesShow', array('id' => $this->getID(), 'format' => 'atom')); - } - - $selfLink = $this->getPref('ostatus', 'self'); - - if (!common_valid_http_url($selfLink)) { - throw new InvalidUrlException($selfLink); - } - - return $selfLink; - } - - public function getObjectType($canonical = false) - { - if (is_null($this->object_type) || $this->object_type==='') { - throw new NoObjectTypeException($this); - } - return ActivityUtils::resolveUri($this->object_type, $canonical); - } - - public function isObjectType(array $types) - { - try { - return ActivityUtils::compareTypes($this->getObjectType(), $types); - } catch (NoObjectTypeException $e) { - return false; - } - } - - /** - * Extract #hashtags from this notice's content and save them to the database. - */ - public function saveTags() - { - /* extract all #hastags */ - $count = preg_match_all('/(?:^|\s)#([\pL\pN_\-\.]{1,64})/u', strtolower($this->content), $match); - if (!$count) { - return true; - } - - /* Add them to the database */ - return $this->saveKnownTags($match[1]); - } - - /** - * Record the given set of hash tags in the db for this notice. - * Given tag strings will be normalized and checked for dupes. - */ - public function saveKnownTags($hashtags) - { - //turn each into their canonical tag - //this is needed to remove dupes before saving e.g. #hash.tag = #hashtag - for ($i = 0; $i < count($hashtags); ++$i) { - /* elide characters we don't want in the tag */ - $hashtags[$i] = common_canonical_tag($hashtags[$i]); - } - - foreach (array_unique($hashtags) as $hashtag) { - $this->saveTag($hashtag); - self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, $hashtag); - } - return true; - } - - /** - * Record a single hash tag as associated with this notice. - * Tag format and uniqueness must be validated by caller. - */ - public function saveTag($hashtag) - { - $tag = new Notice_tag(); - $tag->notice_id = $this->id; - $tag->tag = $hashtag; - $tag->created = $this->created; - $id = $tag->insert(); - - if (!$id) { - // TRANS: Server exception. %s are the error details. - throw new ServerException(sprintf( - _('Database error inserting hashtag: %s.'), - $last_error->message - )); - return; - } - - // if it's saved, blow its cache - $tag->blowCache(false); - } - - /** - * Save a new notice and push it out to subscribers' inboxes. - * Poster's permissions are checked before sending. - * - * @param int $profile_id Profile ID of the poster - * @param string $content source message text; links may be shortened - * per current user's preference - * @param string $source source key ('web', 'api', etc) - * @param array $options Associative array of optional properties: - * string 'created' timestamp of notice; defaults to now - * int 'is_local' source/gateway ID, one of: - * Notice::LOCAL_PUBLIC - Local, ok to appear in public timeline - * Notice::REMOTE - Sent from a remote service; - * hide from public timeline but show in - * local "and friends" timelines - * Notice::LOCAL_NONPUBLIC - Local, but hide from public timeline - * Notice::GATEWAY - From another non-OStatus service; - * will not appear in public views - * float 'lat' decimal latitude for geolocation - * float 'lon' decimal longitude for geolocation - * int 'location_id' geoname identifier - * int 'location_ns' geoname namespace to interpret location_id - * int 'reply_to'; notice ID this is a reply to - * int 'repeat_of'; notice ID this is a repeat of - * string 'uri' unique ID for notice; a unique tag uri (can be url or anything too) - * string 'url' permalink to notice; defaults to local notice URL - * string 'rendered' rendered HTML version of content - * array 'replies' list of profile URIs for reply delivery in - * place of extracting @-replies from content. - * array 'groups' list of group IDs to deliver to, in place of - * extracting ! tags from content - * array 'tags' list of hashtag strings to save with the notice - * in place of extracting # tags from content - * array 'urls' list of attached/referred URLs to save with the - * notice in place of extracting links from content - * boolean 'distribute' whether to distribute the notice, default true - * string 'object_type' URL of the associated object type (default ActivityObject::NOTE) - * string 'verb' URL of the associated verb (default ActivityVerb::POST) - * int 'scope' Scope bitmask; default to SITE_SCOPE on private sites, 0 otherwise - * - * @fixme tag override - * - * @return Notice - * @throws ClientException - */ - public static function saveNew($profile_id, $content, $source, array $options = null) - { - $defaults = array('uri' => null, - 'url' => null, - 'self' => null, - 'conversation' => null, // URI of conversation - 'reply_to' => null, // This will override convo URI if the parent is known - 'repeat_of' => null, // This will override convo URI if the repeated notice is known - 'scope' => null, - 'distribute' => true, - 'object_type' => null, - 'verb' => null); - - if (!empty($options) && is_array($options)) { - $options = array_merge($defaults, $options); - extract($options); - } else { - extract($defaults); - } - - if (!isset($is_local)) { - $is_local = Notice::LOCAL_PUBLIC; - } - - $profile = Profile::getKV('id', $profile_id); - if (!$profile instanceof Profile) { - // TRANS: Client exception thrown when trying to save a notice for an unknown user. - throw new ClientException(_('Problem saving notice. Unknown user.')); - } - - $user = User::getKV('id', $profile_id); - if ($user instanceof User) { - // Use the local user's shortening preferences, if applicable. - $final = $user->shortenLinks($content); - } else { - $final = common_shorten_links($content); - } - - if (Notice::contentTooLong($final)) { - // TRANS: Client exception thrown if a notice contains too many characters. - throw new ClientException(_('Problem saving notice. Too long.')); - } - - if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) { - common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.'); - // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame. - throw new ClientException(_('Too many notices too fast; take a breather '. - 'and post again in a few minutes.')); - } - - if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) { - common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.'); - // TRANS: Client exception thrown when a user tries to post too many duplicate notices in a given time frame. - throw new ClientException(_('Too many duplicate messages too quickly;'. - ' take a breather and post again in a few minutes.')); - } - - if (!$profile->hasRight(Right::NEWNOTICE)) { - common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $profile->nickname); - - // TRANS: Client exception thrown when a user tries to post while being banned. - throw new ClientException(_('You are banned from posting notices on this site.'), 403); - } - - $notice = new Notice(); - $notice->profile_id = $profile_id; - - if ($source && in_array($source, common_config('public', 'autosource'))) { - $notice->is_local = Notice::LOCAL_NONPUBLIC; - } else { - $notice->is_local = $is_local; - } - - if (!empty($created)) { - $notice->created = $created; - } else { - $notice->created = common_sql_now(); - } - - if (!$notice->isLocal()) { - // Only do these checks for non-local notices. Local notices will generate these values later. - if (empty($uri)) { - throw new ServerException('No URI for remote notice. Cannot accept that.'); - } - } - - $notice->content = $final; - - $notice->source = $source; - $notice->uri = $uri; - $notice->url = $url; - - // Get the groups here so we can figure out replies and such - if (!isset($groups)) { - $groups = User_group::idsFromText($notice->content, $profile); - } - - $reply = null; - - // Handle repeat case - - if (!empty($options['repeat_of'])) { - - // Check for a private one - - $repeat = Notice::getByID($options['repeat_of']); - - if ($profile->sameAs($repeat->getProfile())) { - // TRANS: Client error displayed when trying to repeat an own notice. - throw new ClientException(_('You cannot repeat your own notice.')); - } - - if ($repeat->scope != Notice::SITE_SCOPE && - $repeat->scope != Notice::PUBLIC_SCOPE) { - // TRANS: Client error displayed when trying to repeat a non-public notice. - throw new ClientException(_('Cannot repeat a private notice.'), 403); - } - - if (!$repeat->inScope($profile)) { - // The generic checks above should cover this, but let's be sure! - // TRANS: Client error displayed when trying to repeat a notice you cannot access. - throw new ClientException(_('Cannot repeat a notice you cannot read.'), 403); - } - - if ($profile->hasRepeated($repeat)) { - // TRANS: Client error displayed when trying to repeat an already repeated notice. - throw new ClientException(_('You already repeated that notice.')); - } - - $notice->repeat_of = $repeat->id; - $notice->conversation = $repeat->conversation; - } else { - $reply = null; - - // If $reply_to is specified, we check that it exists, and then - // return it if it does - if (!empty($reply_to)) { - $reply = Notice::getKV('id', $reply_to); - } elseif (in_array($source, array('xmpp', 'mail', 'sms'))) { - // If the source lacks capability of sending the "reply_to" - // metadata, let's try to find an inline replyto-reference. - $reply = self::getInlineReplyTo($profile, $final); - } - - if ($reply instanceof Notice) { - if (!$reply->inScope($profile)) { - // TRANS: Client error displayed when trying to reply to a notice a the target has no access to. - // TRANS: %1$s is a user nickname, %2$d is a notice ID (number). - throw new ClientException(sprintf( - _('%1$s has no access to notice %2$d.'), - $profile->nickname, - $reply->id - ), 403); - } - - // If it's a repeat, the reply_to should be to the original - if ($reply->isRepeat()) { - $notice->reply_to = $reply->repeat_of; - } else { - $notice->reply_to = $reply->id; - } - // But the conversation ought to be the same :) - $notice->conversation = $reply->conversation; - - // If the original is private to a group, and notice has - // no group specified, make it to the same group(s) - - if (empty($groups) && ($reply->scope & Notice::GROUP_SCOPE)) { - $groups = array(); - $replyGroups = $reply->getGroups(); - foreach ($replyGroups as $group) { - if ($profile->isMember($group)) { - $groups[] = $group->id; - } - } - } - - // Scope set below - } - - // If we don't know the reply, we might know the conversation! - // This will happen if a known remote user replies to an - // unknown remote user - within a known conversation. - if (empty($notice->conversation) and !empty($options['conversation'])) { - $conv = Conversation::getKV('uri', $options['conversation']); - if ($conv instanceof Conversation) { - common_debug('Conversation stitched together from (probably) a reply to unknown remote user. Activity creation time ('.$notice->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); - } else { - // Conversation entry with specified URI was not found, so we must create it. - common_debug('Conversation URI not found, so we will create it with the URI given in the options to Notice::saveNew: '.$options['conversation']); - $convctx = new ActivityContext(); - $convctx->conversation = $options['conversation']; - if (array_key_exists('conversation_url', $options)) { - $convctx->conversation_url = $options['conversation_url']; - } - // The insert in Conversation::create throws exception on failure - $conv = Conversation::create($convctx, $notice->created); - } - $notice->conversation = $conv->getID(); - unset($conv); - } - } - - // If it's not part of a conversation, it's the beginning of a new conversation. - if (empty($notice->conversation)) { - $conv = Conversation::create(); - $notice->conversation = $conv->getID(); - unset($conv); - } - - - $notloc = new Notice_location(); - if (!empty($lat) && !empty($lon)) { - $notloc->lat = $lat; - $notloc->lon = $lon; - } - - if (!empty($location_ns) && !empty($location_id)) { - $notloc->location_id = $location_id; - $notloc->location_ns = $location_ns; - } - - if (!empty($rendered)) { - $notice->rendered = $rendered; - } else { - $notice->rendered = common_render_content( - $final, - $notice->getProfile(), - ($notice->hasParent() ? $notice->getParent() : null) - ); - } - - if (empty($verb)) { - if ($notice->isRepeat()) { - $notice->verb = ActivityVerb::SHARE; - $notice->object_type = ActivityObject::ACTIVITY; - } else { - $notice->verb = ActivityVerb::POST; - } - } else { - $notice->verb = $verb; - } - - if (empty($object_type)) { - $notice->object_type = (empty($notice->reply_to)) ? ActivityObject::NOTE : ActivityObject::COMMENT; - } else { - $notice->object_type = $object_type; - } - - if (is_null($scope) && $reply instanceof Notice) { - $notice->scope = $reply->scope; - } else { - $notice->scope = $scope; - } - - $notice->scope = self::figureOutScope($profile, $groups, $notice->scope); - - if (Event::handle('StartNoticeSave', array(&$notice))) { - - // XXX: some of these functions write to the DB - - try { - $notice->insert(); // throws exception on failure, if successful we have an ->id - - if (($notloc->lat && $notloc->lon) || ($notloc->location_id && $notloc->location_ns)) { - $notloc->notice_id = $notice->getID(); - $notloc->insert(); // store the notice location if it had any information - } - } catch (Exception $e) { - // Let's test if we managed initial insert, which would imply - // failing on some update-part (check 'insert()'). Delete if - // something had been stored to the database. - if (!empty($notice->id)) { - $notice->delete(); - } - throw $e; - } - } - - if ($self && common_valid_http_url($self)) { - $notice->setPref('ostatus', 'self', $self); - } - - // Only save 'attention' and metadata stuff (URLs, tags...) stuff if - // the activityverb is a POST (since stuff like repeat, favorite etc. - // reasonably handle notifications themselves. - if (ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST))) { - if (isset($replies)) { - $notice->saveKnownReplies($replies); - } else { - $notice->saveReplies(); - } - - if (isset($tags)) { - $notice->saveKnownTags($tags); - } else { - $notice->saveTags(); - } - - // Note: groups may save tags, so must be run after tags are saved - // to avoid errors on duplicates. - // Note: groups should always be set. - - $notice->saveKnownGroups($groups); - - if (isset($urls)) { - $notice->saveKnownUrls($urls); - } else { - $notice->saveUrls(); - } - } - - if ($distribute) { - // Prepare inbox delivery, may be queued to background. - $notice->distribute(); - } - - return $notice; - } - - public static function saveActivity(Activity $act, Profile $actor, array $options = []) - { - // First check if we're going to let this Activity through from the specific actor - if (!$actor->hasRight(Right::NEWNOTICE)) { - common_log(LOG_WARNING, "Attempted post from user disallowed to post: " . $actor->getNickname()); - - // TRANS: Client exception thrown when a user tries to post while being banned. - throw new ClientException(_m('You are banned from posting notices on this site.'), 403); - } - if (common_config('throttle', 'enabled') && !self::checkEditThrottle($actor->id)) { - common_log(LOG_WARNING, 'Excessive posting by profile #' . $actor->id . '; throttled.'); - // TRANS: Client exception thrown when a user tries to post too many notices in a given time frame. - throw new ClientException(_m('Too many notices too fast; take a breather '. - 'and post again in a few minutes.')); - } - - // Get ActivityObject properties - $actobj = null; - if (!empty($act->id)) { - // implied object - $options['uri'] = $act->id; - $options['url'] = $act->link; - if ($act->selfLink) { - $options['self'] = $act->selfLink; - } - } else { - $actobj = count($act->objects)===1 ? $act->objects[0] : null; - if (!is_null($actobj) && !empty($actobj->id)) { - $options['uri'] = $actobj->id; - if ($actobj->link) { - $options['url'] = $actobj->link; - } elseif (preg_match('!^https?://!', $actobj->id)) { - $options['url'] = $actobj->id; - } - } - if ($actobj->selfLink) { - $options['self'] = $actobj->selfLink; - } - } - - $defaults = array( - 'groups' => array(), - 'is_local' => $actor->isLocal() ? self::LOCAL_PUBLIC : self::REMOTE, - 'mentions' => array(), - 'reply_to' => null, - 'repeat_of' => null, - 'scope' => null, - 'self' => null, - 'source' => 'unknown', - 'tags' => array(), - 'uri' => null, - 'url' => null, - 'urls' => array(), - 'distribute' => true); - - // options will have default values when nothing has been supplied - $options = array_merge($defaults, $options); - foreach (array_keys($defaults) as $key) { - // Only convert the keynames we specify ourselves from 'defaults' array into variables - $$key = $options[$key]; - } - extract($options, EXTR_SKIP); - - // dupe check - $stored = new Notice(); - if (!empty($uri) && !ActivityUtils::compareVerbs($act->verb, array(ActivityVerb::DELETE))) { - $stored->uri = $uri; - if ($stored->find()) { - common_debug('cannot create duplicate Notice URI: '.$stored->uri); - // I _assume_ saving a Notice with a colliding URI means we're really trying to - // save the same notice again... - throw new AlreadyFulfilledException('Notice URI already exists'); - } - } - - // NOTE: Sandboxed users previously got all the notices _during_ - // sandbox period set to to is_local=Notice::LOCAL_NONPUBLIC here. - // Since then we have started just filtering _when_ it gets shown - // instead of creating a mixed jumble of differently scoped notices. - - if ($source && in_array($source, common_config('public', 'autosource'))) { - $stored->is_local = Notice::LOCAL_NONPUBLIC; - } else { - $stored->is_local = intval($is_local); - } - - if (!$stored->isLocal()) { - // Only do these checks for non-local notices. Local notices will generate these values later. - if (!common_valid_http_url($url)) { - common_debug('Bad notice URL: ['.$url.'], URI: ['.$uri.']. Cannot link back to original! This is normal for shared notices etc.'); - } - if (empty($uri)) { - throw new ServerException('No URI for remote notice. Cannot accept that.'); - } - } - - $stored->profile_id = $actor->getID(); - $stored->source = $source; - $stored->uri = $uri; - $stored->url = $url; - $stored->verb = $act->verb; - - // we use mb_strlen because it _might_ be that the content is just the string "0"... - $content = mb_strlen($act->content) ? $act->content : $act->summary; - if (mb_strlen($content)===0 && !is_null($actobj)) { - $content = mb_strlen($actobj->content) ? $actobj->content : $actobj->summary; - } - // Strip out any bad HTML from $content. URI.Base is used to sort out relative URLs. - $stored->rendered = common_purify($content, ['URI.Base' => $stored->url ?: null]); - $stored->content = common_strip_html($stored->getRendered(), true, true); - if (trim($stored->content) === '') { - // TRANS: Error message when the plain text content of a notice has zero length. - throw new ClientException(_('Empty notice content, will not save this.')); - } - unset($content); // garbage collect - - // Maybe a missing act-time should be fatal if the actor is not local? - if (!empty($act->time)) { - $stored->created = common_sql_date($act->time); - } else { - $stored->created = common_sql_now(); - } - - $reply = null; // this will store the in-reply-to Notice if found - $replyUris = []; // this keeps a list of possible URIs to look up - if ($act->context instanceof ActivityContext && !empty($act->context->replyToID)) { - $replyUris[] = $act->context->replyToID; - } - if ($act->target instanceof ActivityObject && !empty($act->target->id)) { - $replyUris[] = $act->target->id; - } - foreach (array_unique($replyUris) as $replyUri) { - $reply = self::getKV('uri', $replyUri); - // Only do remote fetching if we're not a private site - if (!common_config('site', 'private') && !$reply instanceof Notice) { - // the URI is the object we're looking for, $actor is a - // Profile that surely knows of it and &$reply where it - // will be stored when fetched - Event::handle('FetchRemoteNotice', array($replyUri, $actor, &$reply)); - } - // we got what we're in-reply-to now, so let's move on - if ($reply instanceof Notice) { - break; - } - // otherwise reset whatever we might've gotten from the event - $reply = null; - } - unset($replyUris); // garbage collect - - if ($reply instanceof Notice) { - if (!$reply->inScope($actor)) { - // TRANS: Client error displayed when trying to reply to a notice a the target has no access to. - // TRANS: %1$s is a user nickname, %2$d is a notice ID (number). - throw new ClientException(sprintf(_m('%1$s has no right to reply to notice %2$d.'), $actor->getNickname(), $reply->id), 403); - } - - $stored->reply_to = $reply->id; - $stored->conversation = $reply->conversation; - - // If the original is private to a group, and notice has no group specified, - // make it to the same group(s) - if (empty($groups) && ($reply->scope & Notice::GROUP_SCOPE)) { - $replyGroups = $reply->getGroups(); - foreach ($replyGroups as $group) { - if ($actor->isMember($group)) { - $groups[] = $group->id; - } - } - } - - if (is_null($scope)) { - $scope = $reply->scope; - } - } else { - // If we don't know the reply, we might know the conversation! - // This will happen if a known remote user replies to an - // unknown remote user - within a known conversation. - if (empty($stored->conversation) and !empty($act->context->conversation)) { - $conv = Conversation::getKV('uri', $act->context->conversation); - if ($conv instanceof Conversation) { - common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time ('.$stored->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); - } else { - // Conversation entry with specified URI was not found, so we must create it. - common_debug('Conversation URI not found, so we will create it with the URI given in the context of the activity: '.$act->context->conversation); - // The insert in Conversation::create throws exception on failure - $conv = Conversation::create($act->context, $stored->created); - } - $stored->conversation = $conv->getID(); - unset($conv); - } - } - unset($reply); // garbage collect - - // If it's not part of a conversation, it's the beginning of a new conversation. - if (empty($stored->conversation)) { - $conv = Conversation::create(); - $stored->conversation = $conv->getID(); - unset($conv); - } - - $notloc = null; - if ($act->context instanceof ActivityContext) { - if ($act->context->location instanceof Location) { - $notloc = Notice_location::fromLocation($act->context->location); - } - } else { - $act->context = new ActivityContext(); - } - - 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[ActivityContext::ATTN_PUBLIC]); - } else { - $stored->scope = self::figureOutScope($actor, $groups, $scope); - } - - foreach ($act->categories as $cat) { - if ($cat->term) { - $term = common_canonical_tag($cat->term); - if (!empty($term)) { - $tags[] = $term; - } - } - } - - foreach ($act->enclosures as $href) { - // @todo FIXME: Save these locally or....? - $urls[] = $href; - } - - if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) { - if (empty($act->objects[0]->type)) { - // Default type for the post verb is 'note', but we know it's - // a 'comment' if it is in reply to something. - $stored->object_type = empty($stored->reply_to) ? ActivityObject::NOTE : ActivityObject::COMMENT; - } else { - //TODO: Is it safe to always return a relative URI? The - // JSON version of ActivityStreams always use it, so we - // should definitely be able to handle it... - $stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type, true); - } - } - - if (Event::handle('StartNoticeSave', array(&$stored))) { - // XXX: some of these functions write to the DB - - try { - $result = $stored->insert(); // throws exception on error - - if ($notloc instanceof Notice_location) { - $notloc->notice_id = $stored->getID(); - $notloc->insert(); - } - - $orig = clone($stored); // for updating later in this try clause - - $object = null; - Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); - if (empty($object)) { - throw new NoticeSaveException('Unsuccessful call to StoreActivityObject '._ve($stored->getUri()) . ': '._ve($act->asString())); - } - unset($object); - - // If something changed in the Notice during StoreActivityObject - $stored->update($orig); - } catch (Exception $e) { - if (empty($stored->id)) { - common_debug('Failed to save stored object entry in database ('.$e->getMessage().')'); - } else { - common_debug('Failed to store activity object in database ('.$e->getMessage().'), deleting notice id '.$stored->id); - $stored->delete(); - } - throw $e; - } - } - unset($notloc); // garbage collect - - if (!$stored instanceof Notice) { - throw new ServerException('StartNoticeSave did not give back a Notice.'); - } elseif (empty($stored->id)) { - throw new ServerException('Supposedly saved Notice has no ID.'); - } - - if ($self && common_valid_http_url($self)) { - $stored->setPref('ostatus', 'self', $self); - } - - if ($self && common_valid_http_url($self)) { - $stored->setPref('ostatus', 'self', $self); - } - - // Only save 'attention' and metadata stuff (URLs, tags...) stuff if - // the activityverb is a POST (since stuff like repeat, favorite etc. - // reasonably handle notifications themselves. - if (ActivityUtils::compareVerbs($stored->verb, array(ActivityVerb::POST))) { - if (!empty($tags)) { - $stored->saveKnownTags($tags); - } else { - $stored->saveTags(); - } - - // Note: groups may save tags, so must be run after tags are saved - // to avoid errors on duplicates. - $stored->saveAttentions($act->context->attention); - - if (!empty($urls)) { - $stored->saveKnownUrls($urls); - } else { - $stored->saveUrls(); - } - } - - if ($distribute) { - // Prepare inbox delivery, may be queued to background. - $stored->distribute(); - } - - return $stored; - } - - public static function figureOutScope(Profile $actor, array $groups, $scope = null) - { - $scope = is_null($scope) ? self::defaultScope() : intval($scope); - - // For private streams - try { - $user = $actor->getUser(); - // FIXME: We can't do bit comparison with == (Legacy StatusNet thing. Let's keep it for now.) - if ($user->private_stream && ($scope === Notice::PUBLIC_SCOPE || $scope === Notice::SITE_SCOPE)) { - $scope |= Notice::FOLLOWER_SCOPE; - } - } catch (NoSuchUserException $e) { - // TODO: Not a local user, so we don't know about scope preferences... yet! - } - - // Force the scope for private groups - foreach ($groups as $group_id) { - try { - $group = User_group::getByID($group_id); - if ($group->force_scope) { - $scope |= Notice::GROUP_SCOPE; - break; - } - } catch (Exception $e) { - common_log(LOG_ERR, 'Notice figureOutScope threw exception: '.$e->getMessage()); - } - } - - return $scope; - } - - public function blowOnInsert($conversation = false) - { - $this->blowStream('profile:notice_ids:%d', $this->profile_id); - - if ($this->isPublic()) { - $this->blowStream('public'); - $this->blowStream('networkpublic'); - } - - if ($this->conversation) { - self::blow('notice:list-ids:conversation:%s', $this->conversation); - self::blow('conversation:notice_count:%d', $this->conversation); - } - - if ($this->isRepeat()) { - // XXX: we should probably only use one of these - $this->blowStream('notice:repeats:%d', $this->repeat_of); - self::blow('notice:list-ids:repeat_of:%d', $this->repeat_of); - } - - $original = Notice::getKV('id', $this->repeat_of); - - if ($original instanceof Notice) { - $originalUser = User::getKV('id', $original->profile_id); - if ($originalUser instanceof User) { - $this->blowStream('user:repeats_of_me:%d', $originalUser->id); - } - } - - $profile = Profile::getKV($this->profile_id); - - if ($profile instanceof Profile) { - $profile->blowNoticeCount(); - } - - $ptags = $this->getProfileTags(); - foreach ($ptags as $ptag) { - $ptag->blowNoticeStreamCache(); - } - } - - /** - * Clear cache entries related to this notice at delete time. - * Necessary to avoid breaking paging on public, profile timelines. - */ - public function blowOnDelete() - { - $this->blowOnInsert(); - - self::blow('profile:notice_ids:%d;last', $this->profile_id); - - if ($this->isPublic()) { - self::blow('public;last'); - self::blow('networkpublic;last'); - } - - self::blow('fave:by_notice', $this->id); - - if ($this->conversation) { - // In case we're the first, will need to calc a new root. - self::blow('notice:conversation_root:%d', $this->conversation); - } - - $ptags = $this->getProfileTags(); - foreach ($ptags as $ptag) { - $ptag->blowNoticeStreamCache(true); - } - } - - public function blowStream() - { - $c = self::memcache(); - - if (empty($c)) { - return false; - } - - $args = func_get_args(); - $format = array_shift($args); - $keyPart = vsprintf($format, $args); - $cacheKey = Cache::key($keyPart); - $c->delete($cacheKey); - - // delete the "last" stream, too, if this notice is - // older than the top of that stream - - $lastKey = $cacheKey.';last'; - - $lastStr = $c->get($lastKey); - - if ($lastStr !== false) { - $window = explode(',', $lastStr); - $lastID = $window[0]; - $lastNotice = Notice::getKV('id', $lastID); - if (!$lastNotice instanceof Notice // just weird - || strtotime($lastNotice->created) >= strtotime($this->created)) { - $c->delete($lastKey); - } - } - } - - /** save all urls in the notice to the db - * - * follow redirects and save all available file information - * (mimetype, date, size, oembed, etc.) - * - * @return void - */ - public function saveUrls() - { - if (common_config('attachments', 'process_links')) { - common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this); - } - } - - /** - * Save the given URLs as related links/attachments to the db - * - * follow redirects and save all available file information - * (mimetype, date, size, oembed, etc.) - * - * @return void - */ - public function saveKnownUrls($urls) - { - if (common_config('attachments', 'process_links')) { - // @fixme validation? - foreach (array_unique($urls) as $url) { - $this->saveUrl($url, $this); - } - } - } - - /** - * @private callback - */ - public function saveUrl($url, Notice $notice) - { - try { - File::processNew($url, $notice); - } catch (ServerException $e) { - // Could not save URL. Log it? - } - } - - public static function checkDupes($profile_id, $content) - { - $profile = Profile::getKV($profile_id); - if (!$profile instanceof Profile) { - return false; - } - $notice = $profile->getNotices(0, CachingNoticeStream::CACHE_WINDOW); - if (!empty($notice)) { - $last = 0; - while ($notice->fetch()) { - if (time() - strtotime($notice->created) >= common_config('site', 'dupelimit')) { - return true; - } elseif ($notice->content === $content) { - return false; - } - } - } - // If we get here, oldest item in cache window is not - // old enough for dupe limit; do direct check against DB - $notice = new Notice(); - $notice->profile_id = $profile_id; - $notice->content = $content; - $threshold = common_sql_date(time() - common_config('site', 'dupelimit')); - $notice->whereAdd(sprintf("created > '%s'", $notice->escape($threshold))); - - $cnt = $notice->count(); - return ($cnt == 0); - } - - public static function checkEditThrottle($profile_id) - { - $profile = Profile::getKV($profile_id); - if (!$profile instanceof Profile) { - return false; - } - // Get the Nth notice - $notice = $profile->getNotices(common_config('throttle', 'count') - 1, 1); - if ($notice && $notice->fetch()) { - // If the Nth notice was posted less than timespan seconds ago - if (time() - strtotime($notice->created) <= common_config('throttle', 'timespan')) { - // Then we throttle - return false; - } - } - // Either not N notices in the stream, OR the Nth was not posted within timespan seconds - return true; - } - - protected $_attachments = []; - - public function attachments() - { - if (isset($this->_attachments[$this->id])) { - return $this->_attachments[$this->id]; - } - - $f2ps = File_to_post::listGet('post_id', array($this->id)); - $ids = []; - foreach ($f2ps[$this->id] as $f2p) { - $ids[] = $f2p->file_id; - } - - return $this->_setAttachments(File::multiGet('id', $ids)->fetchAll()); - } - - public function _setAttachments(array $attachments) - { - return $this->_attachments[$this->id] = $attachments; - } - - public static function publicStream($offset = 0, $limit = 20, $since_id = null, $max_id = null) - { - $stream = new PublicNoticeStream(); - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - public static function conversationStream($id, $offset = 0, $limit = 20, $since_id = null, $max_id = null, Profile $scoped = null) - { - $stream = new ConversationNoticeStream($id, $scoped); - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - /** - * Is this notice part of an active conversation? - * - * @return boolean true if other messages exist in the same - * conversation, false if this is the only one - */ - public function hasConversation() - { - if (empty($this->conversation)) { - // this notice is not part of a conversation apparently - // FIXME: all notices should have a conversation value, right? - return false; - } - - //FIXME: Get the Profile::current() stuff some other way - // to avoid confusion between queue processing and session. - $notice = self::conversationStream($this->conversation, 1, 1, null, null, Profile::current()); - - // if our "offset 1, limit 1" query got a result, return true else false - return $notice->N > 0; - } - - /** - * Grab the earliest notice from this conversation. - * - * @return Notice or null - */ - public function conversationRoot($profile = -1) - { - // XXX: can this happen? - - if (empty($this->conversation)) { - return null; - } - - // Get the current profile if not specified - - if (is_int($profile) && $profile == -1) { - $profile = Profile::current(); - } - - // If this notice is out of scope, no root for you! - - if (!$this->inScope($profile)) { - return null; - } - - // If this isn't a reply to anything, then it's its own - // root if it's the earliest notice in the conversation: - - if (empty($this->reply_to)) { - $root = new Notice; - $root->conversation = $this->conversation; - $root->orderBy('created, id'); - $root->limit(0, 1); - $root->find(true); // true means "fetch first result" - $root->free(); - return $root; - } - - if (is_null($profile)) { - $keypart = sprintf('notice:conversation_root:%d:null', $this->id); - } else { - $keypart = sprintf( - 'notice:conversation_root:%d:%d', - $this->id, - $profile->id - ); - } - - $root = self::cacheGet($keypart); - - if ($root !== false && $root->inScope($profile)) { - return $root; - } - - $last = $this; - while (true) { - try { - $parent = $last->getParent(); - if ($parent->inScope($profile)) { - $last = $parent; - continue; - } - } catch (NoParentNoticeException $e) { - // Latest notice has no parent - } catch (NoResultException $e) { - // Notice was not found, so we can't go further up in the tree. - // FIXME: Maybe we should do this in a more stable way where deleted - // notices won't break conversation chains? - } - // No parent, or parent out of scope - $root = $last; - break; - } - - self::cacheSet($keypart, $root); - - return $root; - } - - /** - * Pull up a full list of local recipients who will be getting - * this notice in their inbox. Results will be cached, so don't - * change the input data wily-nilly! - * - * @param array $groups optional list of Group objects; - * if left empty, will be loaded from group_inbox records - * @param array $recipient optional list of reply profile ids - * if left empty, will be loaded from reply records - * @return array associating recipient user IDs with an inbox source constant - */ - public function whoGets(array $groups = null, array $recipients = null) - { - $c = self::memcache(); - - if (!empty($c)) { - $ni = $c->get(Cache::key('notice:who_gets:'.$this->id)); - if ($ni !== false) { - return $ni; - } - } - - if (is_null($recipients)) { - $recipients = $this->getReplies(); - } - - $ni = array(); - - // Give plugins a chance to add folks in at start... - if (Event::handle('StartNoticeWhoGets', array($this, &$ni))) { - $users = $this->getSubscribedUsers(); - foreach ($users as $id) { - $ni[$id] = NOTICE_INBOX_SOURCE_SUB; - } - - if (is_null($groups)) { - $groups = $this->getGroups(); - } - foreach ($groups as $group) { - $users = $group->getUserMembers(); - foreach ($users as $id) { - if (!array_key_exists($id, $ni)) { - $ni[$id] = NOTICE_INBOX_SOURCE_GROUP; - } - } - } - - $ptAtts = $this->getAttentionsFromProfileTags(); - foreach ($ptAtts as $key=>$val) { - if (!array_key_exists($key, $ni)) { - $ni[$key] = $val; - } - } - - foreach ($recipients as $recipient) { - if (!array_key_exists($recipient, $ni)) { - $ni[$recipient] = NOTICE_INBOX_SOURCE_REPLY; - } - } - - // Exclude any deleted, non-local, or blocking recipients. - $profile = $this->getProfile(); - $originalProfile = null; - if ($this->isRepeat()) { - // Check blocks against the original notice's poster as well. - $original = Notice::getKV('id', $this->repeat_of); - if ($original instanceof Notice) { - $originalProfile = $original->getProfile(); - } - } - - foreach ($ni as $id => $source) { - try { - $user = User::getKV('id', $id); - if (!$user instanceof User || - $user->hasBlocked($profile) || - ($originalProfile && $user->hasBlocked($originalProfile))) { - unset($ni[$id]); - } - } catch (UserNoProfileException $e) { - // User doesn't have a profile; invalid; skip them. - unset($ni[$id]); - } - } - - // Give plugins a chance to filter out... - Event::handle('EndNoticeWhoGets', array($this, &$ni)); - } - - if (!empty($c)) { - // XXX: pack this data better - $c->set(Cache::key('notice:who_gets:'.$this->id), $ni); - } - - return $ni; - } - - public function getSubscribedUsers() - { - $user = new User(); - - $user->query(sprintf( - 'SELECT id FROM %1$s INNER JOIN subscription ' . - 'ON %1$s.id = subscription.subscriber ' . - 'WHERE subscription.subscribed = %2$d ', - $user->escapedTableName(), - $this->profile_id - )); - - $ids = []; - - while ($user->fetch()) { - $ids[] = $user->id; - } - - $user->free(); - - return $ids; - } - - public function getProfileTags() - { - $ptags = array(); - try { - $profile = $this->getProfile(); - $list = $profile->getOtherTags($profile); - - 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; - } - - public function getAttentionsFromProfileTags() - { - $ni = array(); - $ptags = $this->getProfileTags(); - foreach ($ptags as $ptag) { - $users = $ptag->getUserSubscribers(); - foreach ($users as $id) { - $ni[$id] = NOTICE_INBOX_SOURCE_PROFILE_TAG; - } - } - return $ni; - } - - /** - * Record this notice to the given group inboxes for delivery. - * Overrides the regular parsing of !group markup. - * - * @param string $group_ids - * @fixme might prefer URIs as identifiers, as for replies? - * best with generalizations on user_group to support - * remote groups better. - */ - public function saveKnownGroups(array $group_ids) - { - $groups = array(); - foreach (array_unique($group_ids) as $id) { - $group = User_group::getKV('id', $id); - if ($group instanceof User_group) { - common_log(LOG_DEBUG, "Local delivery to group id $id, $group->nickname"); - $result = $this->addToGroupInbox($group); - if (!$result) { - common_log_db_error($gi, 'INSERT', __FILE__); - } - - if (common_config('group', 'addtag')) { - // we automatically add a tag for every group name, too - common_debug('Adding hashtag matching group nickname: '._ve($group->getNickname())); - $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($group->getNickname()), - 'notice_id' => $this->getID())); - - if (is_null($tag)) { - $this->saveTag($group->getNickname()); - } - } - - $groups[] = clone($group); - } else { - common_log(LOG_ERR, "Local delivery to group id $id skipped, doesn't exist"); - } - } - - return $groups; - } - - public function addToGroupInbox(User_group $group) - { - $gi = Group_inbox::pkeyGet(array('group_id' => $group->id, - 'notice_id' => $this->id)); - - if (!$gi instanceof Group_inbox) { - $gi = new Group_inbox(); - - $gi->group_id = $group->id; - $gi->notice_id = $this->id; - $gi->created = $this->created; - - $result = $gi->insert(); - - if (!$result) { - common_log_db_error($gi, 'INSERT', __FILE__); - // TRANS: Server exception thrown when an update for a group inbox fails. - throw new ServerException(_('Problem saving group inbox.')); - } - - self::blow('user_group:notice_ids:%d', $gi->group_id); - } - - return true; - } - - public function saveAttentions(array $uris) - { - foreach ($uris as $uri=>$type) { - try { - $target = Profile::fromUri($uri); - } catch (UnknownUriException $e) { - common_log(LOG_WARNING, "Unable to determine profile for URI '$uri'"); - continue; - } - - try { - $this->saveAttention($target); - } catch (AlreadyFulfilledException $e) { - common_debug('Attention already exists: ' . var_export($e->getMessage(), true)); - } catch (Exception $e) { - common_log(LOG_ERR, "Could not save notice id=={$this->getID()} attention for profile id=={$target->getID()}: {$e->getMessage()}"); - } - } - } - - /** - * Saves an attention for a profile (user or group) which means - * it shows up in their home feed and such. - */ - public function saveAttention(Profile $target, $reason = null) - { - if ($target->isGroup()) { - // FIXME: Make sure we check (for both local and remote) users are in the groups they send to! - - // legacy notification method, will still be in use for quite a while I think - $this->addToGroupInbox($target->getGroup()); - } else { - if ($target->hasBlocked($this->getProfile())) { - common_log(LOG_INFO, "Not saving reply to profile {$target->id} ($uri) from sender {$sender->id} because of a block."); - return false; - } - } - - if ($target->isLocal()) { - // legacy notification method, will still be in use for quite a while I think - $this->saveReply($target->getID()); - } - - $att = Attention::saveNew($this, $target, $reason); - return true; - } - - /** - * Save reply records indicating that this notice needs to be - * delivered to the local users with the given URIs. - * - * Since this is expected to be used when saving foreign-sourced - * messages, we won't deliver to any remote targets as that's the - * source service's responsibility. - * - * Mail notifications etc will be handled later. - * - * @param array $uris Array of unique identifier URIs for recipients - */ - public function saveKnownReplies(array $uris) - { - if (empty($uris)) { - return; - } - - $sender = $this->getProfile(); - - foreach (array_unique($uris) as $uri) { - try { - $profile = Profile::fromUri($uri); - } catch (UnknownUriException $e) { - common_log(LOG_WARNING, "Unable to determine profile for URI '$uri'"); - continue; - } - - if ($profile->hasBlocked($sender)) { - common_log(LOG_INFO, "Not saving reply to profile {$profile->id} ($uri) from sender {$sender->id} because of a block."); - continue; - } - - $this->saveReply($profile->getID()); - self::blow('reply:stream:%d', $profile->getID()); - } - } - - /** - * Pull @-replies from this message's content in StatusNet markup format - * and save reply records indicating that this message needs to be - * delivered to those users. - * - * Mail notifications to local profiles will be sent later. - * - * @return array of integer profile IDs - */ - - public function saveReplies() - { - $sender = $this->getProfile(); - - $replied = array(); - - // If it's a reply, save for the replied-to author - try { - $parent = $this->getParent(); - $parentauthor = $parent->getProfile(); - $this->saveReply($parentauthor->getID()); - $replied[$parentauthor->getID()] = 1; - self::blow('reply:stream:%d', $parentauthor->getID()); - } catch (NoParentNoticeException $e) { - // Not a reply, since it has no parent! - $parent = null; - } catch (NoResultException $e) { - // Parent notice was probably deleted - $parent = null; - } - - // @todo ideally this parser information would only - // be calculated once. - - $mentions = common_find_mentions($this->content, $sender, $parent); - - foreach ($mentions as $mention) { - foreach ($mention['mentioned'] as $mentioned) { - - // skip if they're already covered - if (array_key_exists($mentioned->id, $replied)) { - continue; - } - - // Don't save replies from blocked profile to local user - if ($mentioned->hasBlocked($sender)) { - continue; - } - - $this->saveReply($mentioned->id); - $replied[$mentioned->id] = 1; - self::blow('reply:stream:%d', $mentioned->id); - } - } - - $recipientIds = array_keys($replied); - - return $recipientIds; - } - - public function saveReply($profileId) - { - $reply = new Reply(); - - $reply->notice_id = $this->id; - $reply->profile_id = $profileId; - $reply->modified = $this->created; - - $reply->insert(); - - return $reply; - } - - protected $_attentionids = array(); - - /** - * Pull the complete list of known activity context attentions for this notice. - * - * @return array of integer profile ids (also group profiles) - */ - public function getAttentionProfileIDs() - { - if (!isset($this->_attentionids[$this->getID()])) { - $atts = Attention::multiGet('notice_id', array($this->getID())); - // (array)null means empty array - $this->_attentionids[$this->getID()] = (array)$atts->fetchAll('profile_id'); - } - return $this->_attentionids[$this->getID()]; - } - - protected $_replies = array(); - - /** - * Pull the complete list of @-mentioned profile IDs for this notice. - * - * @return array of integer profile ids - */ - public function getReplies() - { - if (!isset($this->_replies[$this->getID()])) { - $mentions = Reply::multiGet('notice_id', array($this->getID())); - $this->_replies[$this->getID()] = $mentions->fetchAll('profile_id'); - } - return $this->_replies[$this->getID()]; - } - - public function _setReplies($replies) - { - $this->_replies[$this->getID()] = $replies; - } - - /** - * Pull the complete list of @-reply targets for this notice. - * - * @return array of Profiles - */ - public function getAttentionProfiles() - { - $ids = array_unique(array_merge($this->getReplies(), $this->getGroupProfileIDs(), $this->getAttentionProfileIDs())); - - $profiles = Profile::multiGet('id', (array)$ids); - - return $profiles->fetchAll(); - } - - /** - * Send e-mail notifications to local @-reply targets. - * - * Replies must already have been saved; this is expected to be run - * from the distrib queue handler. - */ - public function sendReplyNotifications() - { - // Don't send reply notifications for repeats - if ($this->isRepeat()) { - return array(); - } - - $recipientIds = $this->getReplies(); - if (Event::handle('StartNotifyMentioned', array($this, &$recipientIds))) { - require_once INSTALLDIR . '/lib/util/mail.php'; - - foreach ($recipientIds as $recipientId) { - try { - $user = User::getByID($recipientId); - mail_notify_attn($user->getProfile(), $this); - } catch (NoResultException $e) { - // No such user - } - } - Event::handle('EndNotifyMentioned', array($this, $recipientIds)); - } - } - - /** - * Pull list of Profile IDs of groups this notice addresses. - * - * @return array of Group _profile_ IDs - */ - - public function getGroupProfileIDs() - { - $ids = array(); - - foreach ($this->getGroups() as $group) { - $ids[] = $group->profile_id; - } - - return $ids; - } - - /** - * Pull list of groups this notice needs to be delivered to, - * as previously recorded by saveKnownGroups(). - * - * @return array of Group objects - */ - - protected $_groups = array(); - - public function getGroups() - { - // Don't save groups for repeats - - if (!empty($this->repeat_of)) { - return array(); - } - - if (isset($this->_groups[$this->id])) { - return $this->_groups[$this->id]; - } - - $gis = Group_inbox::listGet('notice_id', array($this->id)); - - $ids = []; - - foreach ($gis[$this->id] as $gi) { - $ids[] = $gi->group_id; - } - - $groups = User_group::multiGet('id', $ids); - $this->_groups[$this->id] = $groups->fetchAll(); - return $this->_groups[$this->id]; - } - - public function _setGroups($groups) - { - $this->_groups[$this->id] = $groups; - } - - /** - * Convert a notice into an activity for export. - * - * @param Profile $scoped The currently logged in/scoped profile - * - * @return Activity activity object representing this Notice. - * @throws ClientException - * @throws ServerException - */ - - public function asActivity(Profile $scoped = null) - { - $act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id)); - - if ($act instanceof Activity) { - return $act; - } - $act = new Activity(); - - if (Event::handle('StartNoticeAsActivity', array($this, $act, $scoped))) { - $act->id = $this->uri; - $act->time = strtotime($this->created); - try { - $act->link = $this->getUrl(); - } catch (InvalidUrlException $e) { - // The notice is probably a share or similar, which don't - // have a representational URL of their own. - } - $act->content = common_xml_safe_str($this->getRendered()); - - $profile = $this->getProfile(); - - $act->actor = $profile->asActivityObject(); - $act->actor->extra[] = $profile->profileInfo($scoped); - - $act->verb = $this->verb; - - if (!$this->repeat_of) { - $act->objects[] = $this->asActivityObject(); - } - - // XXX: should this be handled by default processing for object entry? - - // Categories - - $tags = $this->getTags(); - - foreach ($tags as $tag) { - $cat = new AtomCategory(); - $cat->term = $tag; - - $act->categories[] = $cat; - } - - // Enclosures - // XXX: use Atom Media and/or File activity objects instead - - $attachments = $this->attachments(); - - foreach ($attachments as $attachment) { - // Include local attachments in Activity - if (!empty($attachment->filename)) { - $act->enclosures[] = $attachment->getEnclosure(); - } - } - - $ctx = new ActivityContext(); - - try { - $reply = $this->getParent(); - $ctx->replyToID = $reply->getUri(); - $ctx->replyToUrl = $reply->getUrl(true); // true for fallback to local URL, less messy - } catch (NoParentNoticeException $e) { - // This is not a reply to something - } catch (NoResultException $e) { - // Parent notice was probably deleted - } - - try { - $ctx->location = Notice_location::locFromStored($this); - } catch (ServerException $e) { - $ctx->location = null; - } - - $conv = null; - - if (!empty($this->conversation)) { - $conv = Conversation::getKV('id', $this->conversation); - if ($conv instanceof Conversation) { - $ctx->conversation = $conv->uri; - $ctx->conversation_url = $conv->url; - } - } - - // This covers the legacy getReplies and getGroups too which get their data - // from entries stored via Notice::saveNew (which we want to move away from)... - foreach ($this->getAttentionProfiles() as $target) { - // User and group profiles which get the attention of this notice - $ctx->attention[$target->getUri()] = $target->getObjectType(); - } - - switch ($this->scope) { - case Notice::PUBLIC_SCOPE: - $ctx->attention[ActivityContext::ATTN_PUBLIC] = ActivityObject::COLLECTION; - break; - case Notice::FOLLOWER_SCOPE: - $surl = common_local_url("subscribers", array('nickname' => $profile->nickname)); - $ctx->attention[$surl] = ActivityObject::COLLECTION; - break; - } - - $act->context = $ctx; - - $source = $this->getSource(); - - if ($source instanceof Notice_source) { - $act->generator = ActivityObject::fromNoticeSource($source); - } - - // Source - - $atom_feed = $profile->getAtomFeed(); - - if (!empty($atom_feed)) { - $act->source = new ActivitySource(); - - // XXX: we should store the actual feed ID - - $act->source->id = $atom_feed; - - // XXX: we should store the actual feed title - - $act->source->title = $profile->getBestName(); - - $act->source->links['alternate'] = $profile->profileurl; - $act->source->links['self'] = $atom_feed; - - $act->source->icon = $profile->avatarUrl(AVATAR_PROFILE_SIZE); - - $notice = $profile->getCurrentNotice(); - - if ($notice instanceof Notice) { - $act->source->updated = self::utcDate($notice->created); - } - - $user = User::getKV('id', $profile->id); - - if ($user instanceof User) { - $act->source->links['license'] = common_config('license', 'url'); - } - } - - try { - $act->selfLink = $this->getSelfLink(); - } catch (InvalidUrlException $e) { - $act->selfLink = null; - } - if ($this->isLocal()) { - $act->editLink = $act->selfLink; - } - - Event::handle('EndNoticeAsActivity', array($this, $act, $scoped)); - } - - self::cacheSet(Cache::codeKey('notice:as-activity:'.$this->id), $act); - - return $act; - } - - // This has gotten way too long. Needs to be sliced up into functional bits - // or ideally exported to a utility class. - - public function asAtomEntry($namespace = false, $source = false, $author = true, Profile $scoped = null) - { - $act = $this->asActivity($scoped); - $act->extra[] = $this->noticeInfo($scoped); - return $act->asString($namespace, $author, $source); - } - - /** - * Extra notice info for atom entries - * - * Clients use some extra notice info in the atom stream. - * This gives it to them. - * - * @param Profile $scoped The currently logged in/scoped profile - * - * @return array representation of element - */ - - public function noticeInfo(Profile $scoped = null) - { - // local notice ID (useful to clients for ordering) - - $noticeInfoAttr = array('local_id' => $this->id); - - // notice source - - $ns = $this->getSource(); - - if ($ns instanceof Notice_source) { - $noticeInfoAttr['source'] = $ns->code; - if (!empty($ns->url)) { - $noticeInfoAttr['source_link'] = $ns->url; - if (!empty($ns->name)) { - $noticeInfoAttr['source'] = $ns->name; - } - } - } - - // favorite and repeated - - if ($scoped instanceof Profile) { - $noticeInfoAttr['repeated'] = ($scoped->hasRepeated($this)) ? "true" : "false"; - } - - if (!empty($this->repeat_of)) { - $noticeInfoAttr['repeat_of'] = $this->repeat_of; - } - - Event::handle('StatusNetApiNoticeInfo', array($this, &$noticeInfoAttr, $scoped)); - - return array('statusnet:notice_info', $noticeInfoAttr, null); - } - - /** - * Returns an XML string fragment with a reference to a notice as an - * Activity Streams noun object with the given element type. - * - * Assumes that 'activity' namespace has been previously defined. - * - * @param string $element one of 'subject', 'object', 'target' - * @return string - */ - - public function asActivityNoun($element) - { - $noun = $this->asActivityObject(); - return $noun->asString('activity:' . $element); - } - - public function asActivityObject() - { - $object = new ActivityObject(); - - if (Event::handle('StartActivityObjectFromNotice', array($this, &$object))) { - $object->type = $this->object_type ?: ActivityObject::NOTE; - $object->id = $this->getUri(); - //FIXME: = $object->title ?: sprintf(... because we might get a title from StartActivityObjectFromNotice - $object->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $this->getProfile()->getNickname()); - $object->content = $this->getRendered(); - $object->link = $this->getUrl(); - try { - $object->selfLink = $this->getSelfLink(); - } catch (InvalidUrlException $e) { - $object->selfLink = null; - } - - $object->extra[] = array('statusnet:notice_id', null, $this->id); - - Event::handle('EndActivityObjectFromNotice', array($this, &$object)); - } - - if (!$object instanceof ActivityObject) { - common_log(LOG_ERR, 'Notice asActivityObject created something else for uri=='._ve($this->getUri()).': '._ve($object)); - throw new ServerException('Notice asActivityObject created something else.'); - } - - return $object; - } - - /** - * Determine which notice, if any, a new notice is in reply to. - * - * For conversation tracking, we try to see where this notice fits - * in the tree. Beware that this may very well give false positives - * and add replies to wrong threads (if there have been newer posts - * by the same user as we're replying to). - * - * @param Profile $sender Author profile - * @param string $content Final notice content - * - * @return integer ID of replied-to notice, or null for not a reply. - */ - - public static function getInlineReplyTo(Profile $sender, $content) - { - // Is there an initial @ or T? - if (preg_match('/^T ([A-Z0-9]{1,64}) /', $content, $match) - || preg_match('/^@([a-z0-9]{1,64})\s+/', $content, $match)) { - $nickname = common_canonical_nickname($match[1]); - } else { - return null; - } - - // Figure out who that is. - $recipient = common_relative_profile($sender, $nickname, common_sql_now()); - - if ($recipient instanceof Profile) { - // Get their last notice - $last = $recipient->getCurrentNotice(); - if ($last instanceof Notice) { - return $last; - } - // Maybe in the future we want to handle something else below - // so don't return getCurrentNotice() immediately. - } - - return null; - } - - public static function maxContent() - { - $contentlimit = common_config('notice', 'contentlimit'); - // null => use global limit (distinct from 0!) - if (is_null($contentlimit)) { - $contentlimit = common_config('site', 'textlimit'); - } - return $contentlimit; - } - - public static function contentTooLong($content) - { - $contentlimit = self::maxContent(); - return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); - } - - /** - * Convenience function for posting a repeat of an existing message. - * - * @param Profile $repeater Profile which is doing the repeat - * @param string $source: posting source key, eg 'web', 'api', etc - * @return Notice - * - * @throws Exception on failure or permission problems - */ - public function repeat(Profile $repeater, $source) - { - $author = $this->getProfile(); - - // TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'. - // TRANS: %1$s is the repeated user's name, %2$s is the repeated notice. - $content = sprintf( - _('RT @%1$s %2$s'), - $author->getNickname(), - $this->content - ); - - $maxlen = self::maxContent(); - if ($maxlen > 0 && mb_strlen($content) > $maxlen) { - // Web interface and current Twitter API clients will - // pull the original notice's text, but some older - // clients and RSS/Atom feeds will see this trimmed text. - // - // Unfortunately this is likely to lose tags or URLs - // at the end of long notices. - $content = mb_substr($content, 0, $maxlen - 4) . ' ...'; - } - - - // Scope is same as this one's - return self::saveNew( - $repeater->id, - $content, - $source, - ['repeat_of' => $this->id, 'scope' => $this->scope] - ); - } - - // These are supposed to be in chron order! - - public function repeatStream($limit = 100) - { - $cache = Cache::instance(); - - if (empty($cache)) { - $ids = $this->_repeatStreamDirect($limit); - } else { - $idstr = $cache->get(Cache::key('notice:repeats:'.$this->id)); - if ($idstr !== false) { - if (!empty($idstr)) { - $ids = explode(',', $idstr); - } else { - $ids = []; - } - } else { - $ids = $this->_repeatStreamDirect(100); - $cache->set(Cache::key('notice:repeats:'.$this->id), implode(',', $ids)); - } - if ($limit < 100) { - // We do a max of 100, so slice down to limit - $ids = array_slice($ids, 0, $limit); - } - } - - return NoticeStream::getStreamByIds($ids); - } - - public function _repeatStreamDirect($limit) - { - $notice = new Notice(); - - $notice->selectAdd(); // clears it - $notice->selectAdd('id'); - - $notice->repeat_of = $this->id; - - $notice->orderBy('created, id'); // NB: asc! - - if (!is_null($limit)) { - $notice->limit(0, $limit); - } - - return $notice->fetchAll('id'); - } - - public static function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null) - { - $options = array(); - - if (!empty($location_id) && !empty($location_ns)) { - $options['location_id'] = $location_id; - $options['location_ns'] = $location_ns; - - $location = Location::fromId($location_id, $location_ns); - - if ($location instanceof Location) { - $options['lat'] = $location->lat; - $options['lon'] = $location->lon; - } - } elseif (!empty($lat) && !empty($lon)) { - $options['lat'] = $lat; - $options['lon'] = $lon; - - $location = Location::fromLatLon($lat, $lon); - - if ($location instanceof Location) { - $options['location_id'] = $location->location_id; - $options['location_ns'] = $location->location_ns; - } - } elseif (!empty($profile)) { - if (isset($profile->lat) && isset($profile->lon)) { - $options['lat'] = $profile->lat; - $options['lon'] = $profile->lon; - } - - if (isset($profile->location_id) && isset($profile->location_ns)) { - $options['location_id'] = $profile->location_id; - $options['location_ns'] = $profile->location_ns; - } - } - - return $options; - } - - public function clearAttentions() - { - $att = new Attention(); - $att->notice_id = $this->getID(); - - if ($att->find()) { - while ($att->fetch()) { - // Can't do delete() on the object directly since it won't remove all of it - $other = clone($att); - $other->delete(); - } - } - } - - public function clearReplies() - { - $replyNotice = new Notice(); - $replyNotice->reply_to = $this->id; - - //Null any notices that are replies to this notice - - if ($replyNotice->find()) { - while ($replyNotice->fetch()) { - $orig = clone($replyNotice); - $replyNotice->reply_to = $replyNotice->sqlValue('NULL'); - $replyNotice->update($orig); - } - } - - // Reply records - - $reply = new Reply(); - $reply->notice_id = $this->id; - - if ($reply->find()) { - while ($reply->fetch()) { - self::blow('reply:stream:%d', $reply->profile_id); - $reply->delete(); - } - } - - $reply->free(); - } - - public function clearLocation() - { - $loc = new Notice_location(); - $loc->notice_id = $this->id; - - if ($loc->find()) { - $loc->delete(); - } - } - - private function clearPrefs(): void - { - $prefs = new Notice_prefs(); - $prefs->notice_id = $this->id; - - if ($prefs->find()) { - $prefs->delete(); - } - } - - public function clearFiles() - { - $f2p = new File_to_post(); - - $f2p->post_id = $this->id; - - if ($f2p->find()) { - while ($f2p->fetch()) { - $f2p->delete(); - } - } - // FIXME: decide whether to delete File objects - // ...and related (actual) files - } - - public function clearRepeats() - { - $repeatNotice = new Notice(); - $repeatNotice->repeat_of = $this->id; - - //Null any notices that are repeats of this notice - - if ($repeatNotice->find()) { - while ($repeatNotice->fetch()) { - $orig = clone($repeatNotice); - $repeatNotice->repeat_of = $repeatNotice->sqlValue('NULL'); - $repeatNotice->update($orig); - } - } - } - - public function clearTags() - { - $tag = new Notice_tag(); - $tag->notice_id = $this->id; - - if ($tag->find()) { - while ($tag->fetch()) { - self::blow('profile:notice_ids_tagged:%d:%s', $this->profile_id, Cache::keyize($tag->tag)); - self::blow('profile:notice_ids_tagged:%d:%s;last', $this->profile_id, Cache::keyize($tag->tag)); - self::blow('notice_tag:notice_ids:%s', Cache::keyize($tag->tag)); - self::blow('notice_tag:notice_ids:%s;last', Cache::keyize($tag->tag)); - $tag->delete(); - } - } - - $tag->free(); - } - - public function clearGroupInboxes() - { - $gi = new Group_inbox(); - - $gi->notice_id = $this->id; - - if ($gi->find()) { - while ($gi->fetch()) { - self::blow('user_group:notice_ids:%d', $gi->group_id); - $gi->delete(); - } - } - - $gi->free(); - } - - public function distribute() - { - // We always insert for the author so they don't - // have to wait - Event::handle('StartNoticeDistribute', array($this)); - - // If there's a failure, we want to _force_ - // distribution at this point. - try { - $qm = QueueManager::get(); - $qm->enqueue($this, 'distrib'); - } catch (Exception $e) { - // If the exception isn't transient, this - // may throw more exceptions as DQH does - // its own enqueueing. So, we ignore them! - try { - $handler = new DistribQueueHandler(); - $handler->handle($this); - } catch (Exception $e) { - common_log(LOG_ERR, "emergency redistribution resulted in " . $e->getMessage()); - } - // Re-throw so somebody smarter can handle it. - throw $e; - } - } - - public function insert() - { - $result = parent::insert(); - - if ($result === false) { - common_log_db_error($this, 'INSERT', __FILE__); - // TRANS: Server exception thrown when a stored object entry cannot be saved. - throw new ServerException('Could not save Notice'); - } - - // Profile::hasRepeated() abuses pkeyGet(), so we - // have to clear manually - if (!empty($this->repeat_of)) { - $c = self::memcache(); - if (!empty($c)) { - $ck = self::multicacheKey( - 'Notice', - ['profile_id' => $this->profile_id, 'repeat_of' => $this->repeat_of] - ); - $c->delete($ck); - } - } - - // Update possibly ID-dependent columns: URI, conversation - // (now that INSERT has added the notice's local id) - $orig = clone($this); - $changed = false; - - // We can only get here if it's a local notice, since remote notices - // should've bailed out earlier due to lacking a URI. - if (empty($this->uri)) { - $this->uri = sprintf( - '%s%s=%d:%s=%s', - TagURI::mint(), - 'noticeId', - $this->id, - 'objectType', - $this->getObjectType(true) - ); - $changed = true; - } - - if ($changed && $this->update($orig) === false) { - common_log_db_error($notice, 'UPDATE', __FILE__); - // TRANS: Server exception thrown when a notice cannot be updated. - throw new ServerException(_('Problem saving notice.')); - } - - $this->blowOnInsert(); - - return $result; - } - - /** - * Get the source of the notice - * - * @return Notice_source $ns A notice source object. 'code' is the only attribute - * guaranteed to be populated. - */ - public function getSource() - { - if (empty($this->source)) { - return false; - } - - $ns = new Notice_source(); - switch ($this->source) { - case 'web': - case 'xmpp': - case 'mail': - case 'omb': - case 'system': - case 'api': - $ns->code = $this->source; - break; - default: - $ns = Notice_source::getKV($this->source); - if (!$ns) { - $ns = new Notice_source(); - $ns->code = $this->source; - $app = Oauth_application::getKV('name', $this->source); - if ($app) { - $ns->name = $app->name; - $ns->url = $app->source_url; - } - } - break; - } - - return $ns; - } - - /** - * Determine whether the notice was locally created - * - * @return boolean locality - */ - - public function isLocal() - { - $is_local = intval($this->is_local); - return ($is_local === self::LOCAL_PUBLIC || $is_local === self::LOCAL_NONPUBLIC); - } - - public function getScope() - { - return intval($this->scope); - } - - public function isRepeat() - { - return !empty($this->repeat_of); - } - - public function isRepeated() - { - $n = new Notice(); - $n->repeat_of = $this->getID(); - return $n->find() && $n->N > 0; - } - - /** - * Get the list of hash tags saved with this notice. - * - * @return array of strings - */ - public function getTags() - { - $tags = array(); - - $keypart = sprintf('notice:tags:%d', $this->id); - - $tagstr = self::cacheGet($keypart); - - if ($tagstr !== false) { - $tags = explode(',', $tagstr); - } else { - $tag = new Notice_tag(); - $tag->notice_id = $this->id; - if ($tag->find()) { - while ($tag->fetch()) { - $tags[] = $tag->tag; - } - } - self::cacheSet($keypart, implode(',', $tags)); - } - - return $tags; - } - - private static function utcDate($dt) - { - $dateStr = date('d F Y H:i:s', strtotime($dt)); - $d = new DateTime($dateStr, new DateTimeZone('UTC')); - return $d->format(DATE_W3C); - } - - /** - * Look up the creation timestamp for a given notice ID, even - * if it's been deleted. - * - * @param int $id - * @return mixed string recorded creation timestamp, or false if can't be found - */ - public static function getAsTimestamp($id) - { - if (empty($id)) { - throw new EmptyPkeyValueException('Notice', 'id'); - } - - $timestamp = null; - if (Event::handle('GetNoticeSqlTimestamp', array($id, &$timestamp))) { - // getByID throws exception if $id isn't found - $notice = Notice::getByID($id); - $timestamp = $notice->created; - } - - if (empty($timestamp)) { - throw new ServerException('No timestamp found for Notice with id=='._ve($id)); - } - return $timestamp; - } - - /** - * Build an SQL 'where' fragment for timestamp-based sorting from a since_id - * parameter, matching notices posted after the given one (exclusive). - * - * If the referenced notice can't be found, will return false. - * - * @param int $id - * @param string $idField - * @param string $createdField - * @return mixed string or false if no match - */ - public static function whereSinceId($id, $idField='id', $createdField='created') - { - try { - $since = Notice::getAsTimestamp($id); - } catch (Exception $e) { - return false; - } - return sprintf("($createdField = '%s' and $idField > %d) or ($createdField > '%s')", $since, $id, $since); - } - - /** - * Build an SQL 'where' fragment for timestamp-based sorting from a since_id - * parameter, matching notices posted after the given one (exclusive), and - * if necessary add it to the data object's query. - * - * @param DB_DataObject $obj - * @param int $id - * @param string $idField - * @param string $createdField - * @return mixed string or false if no match - */ - public static function addWhereSinceId(DB_DataObject $obj, $id, $idField='id', $createdField='created') - { - $since = self::whereSinceId($id, $idField, $createdField); - if ($since) { - $obj->whereAdd($since); - } - } - - /** - * Build an SQL 'where' fragment for timestamp-based sorting from a max_id - * parameter, matching notices posted before the given one (inclusive). - * - * If the referenced notice can't be found, will return false. - * - * @param int $id - * @param string $idField - * @param string $createdField - * @return mixed string or false if no match - */ - public static function whereMaxId($id, $idField='id', $createdField='created') - { - try { - $max = Notice::getAsTimestamp($id); - } catch (Exception $e) { - return false; - } - return sprintf("($createdField < '%s') or ($createdField = '%s' and $idField <= %d)", $max, $max, $id); - } - - /** - * Build an SQL 'where' fragment for timestamp-based sorting from a max_id - * parameter, matching notices posted before the given one (inclusive), and - * if necessary add it to the data object's query. - * - * @param DB_DataObject $obj - * @param int $id - * @param string $idField - * @param string $createdField - * @return mixed string or false if no match - */ - public static function addWhereMaxId(DB_DataObject $obj, $id, $idField='id', $createdField='created') - { - $max = self::whereMaxId($id, $idField, $createdField); - if ($max) { - $obj->whereAdd($max); - } - } - - public function isPublic() - { - $is_local = intval($this->is_local); - return !($is_local === Notice::LOCAL_NONPUBLIC || $is_local === Notice::GATEWAY); - } - - /** - * Check that the given profile is allowed to read, respond to, or otherwise - * act on this notice. - * - * The $scope member is a bitmask of scopes, representing a logical AND of the - * scope requirement. So, 0x03 (Notice::ADDRESSEE_SCOPE | Notice::SITE_SCOPE) means - * "only visible to people who are mentioned in the notice AND are users on this site." - * Users on the site who are not mentioned in the notice will not be able to see the - * notice. - * - * @param Profile $profile The profile to check; pass null to check for public/unauthenticated users. - * - * @return boolean whether the profile is in the notice's scope - */ - public function inScope($profile) - { - if (is_null($profile)) { - $keypart = sprintf('notice:in-scope-for:%d:null', $this->id); - } else { - $keypart = sprintf('notice:in-scope-for:%d:%d', $this->id, $profile->id); - } - - $result = self::cacheGet($keypart); - - if ($result === false) { - $bResult = false; - if (Event::handle('StartNoticeInScope', array($this, $profile, &$bResult))) { - $bResult = $this->_inScope($profile); - Event::handle('EndNoticeInScope', array($this, $profile, &$bResult)); - } - $result = ($bResult) ? 1 : 0; - self::cacheSet($keypart, $result, 0, 300); - } - - return ($result == 1) ? true : false; - } - - protected function _inScope($profile) - { - $scope = is_null($this->scope) ? self::defaultScope() : $this->getScope(); - - if ($scope === 0 && !$this->getProfile()->isPrivateStream()) { // Not scoping, so it is public. - return !$this->isHiddenSpam($profile); - } - - // If there's scope, anon cannot be in scope - if (empty($profile)) { - return false; - } - - // Author is always in scope - if ($this->profile_id == $profile->id) { - return true; - } - - // Only for users on this site - if (($scope & Notice::SITE_SCOPE) && !$profile->isLocal()) { - return false; - } - - // Only for users mentioned in the notice - if ($scope & Notice::ADDRESSEE_SCOPE) { - $reply = Reply::pkeyGet(array('notice_id' => $this->id, - 'profile_id' => $profile->id)); - - if (!$reply instanceof Reply) { - return false; - } - } - - // Only for members of the given group - if ($scope & Notice::GROUP_SCOPE) { - - // XXX: just query for the single membership - - $groups = $this->getGroups(); - - $foundOne = false; - - foreach ($groups as $group) { - if ($profile->isMember($group)) { - $foundOne = true; - break; - } - } - - if (!$foundOne) { - return false; - } - } - - if ($scope & Notice::FOLLOWER_SCOPE || $this->getProfile()->isPrivateStream()) { - if (!Subscription::exists($profile, $this->getProfile())) { - return false; - } - } - - return !$this->isHiddenSpam($profile); - } - - public function isHiddenSpam($profile) - { - - // Hide posts by silenced users from everyone but moderators. - - if (common_config('notice', 'hidespam')) { - try { - $author = $this->getProfile(); - } catch (Exception $e) { - // If we can't get an author, keep it hidden. - // XXX: technically not spam, but, whatever. - return true; - } - - if ($author->hasRole(Profile_role::SILENCED)) { - if (!$profile instanceof Profile || (($profile->id !== $author->id) && (!$profile->hasRight(Right::REVIEWSPAM)))) { - return true; - } - } - } - - return false; - } - - public function hasParent() - { - try { - $this->getParent(); - } catch (NoParentNoticeException $e) { - return false; - } - return true; - } - - public function getParent() - { - $reply_to_id = null; - - if (empty($this->reply_to)) { - throw new NoParentNoticeException($this); - } - - // The reply_to ID in the table Notice could exist with a number - // however, the replied to notice might not exist in the database. - // Thus we need to catch the exception and throw the NoParentNoticeException else - // the timeline will not display correctly. - try { - $reply_to_id = self::getByID($this->reply_to); - } catch (Exception $e) { - throw new NoParentNoticeException($this); - } - - return $reply_to_id; - } - - /** - * Magic function called at serialize() time. - * - * We use this to drop a couple process-specific references - * from DB_DataObject which can cause trouble in future - * processes. - * - * @return array of variable names to include in serialization. - */ - - public function __sleep() - { - $vars = parent::__sleep(); - $skip = array('_profile', '_groups', '_attachments', '_faves', '_replies', '_repeats'); - return array_diff($vars, $skip); - } - - public static function defaultScope() - { - $scope = common_config('notice', 'defaultscope'); - if (is_null($scope)) { - if (common_config('site', 'private')) { - $scope = 1; - } else { - $scope = 0; - } - } - return $scope; - } - - public static function fillProfiles($notices) - { - $map = self::getProfiles($notices); - foreach ($notices as $entry => $notice) { - try { - if (array_key_exists($notice->profile_id, $map)) { - $notice->_setProfile($map[$notice->profile_id]); - } - } catch (NoProfileException $e) { - common_log(LOG_WARNING, "Failed to fill profile in Notice with non-existing entry for profile_id: {$e->profile_id}"); - unset($notices[$entry]); - } - } - - return array_values($map); - } - - public static function getProfiles(&$notices) - { - $ids = []; - foreach ($notices as $notice) { - $ids[] = $notice->profile_id; - } - $ids = array_unique($ids); - return Profile::pivotGet('id', $ids); - } - - public static function fillGroups(&$notices) - { - $ids = self::_idsOf($notices); - $gis = Group_inbox::listGet('notice_id', $ids); - $gids = []; - - foreach ($gis as $id => $gi) { - foreach ($gi as $g) { - $gids[] = $g->group_id; - } - } - - $gids = array_unique($gids); - $group = User_group::pivotGet('id', $gids); - foreach ($notices as $notice) { - $grps = []; - $gi = $gis[$notice->id]; - foreach ($gi as $g) { - $grps[] = $group[$g->group_id]; - } - $notice->_setGroups($grps); - } - } - - public static function _idsOf(array &$notices) - { - $ids = []; - foreach ($notices as $notice) { - $ids[$notice->id] = true; - } - return array_keys($ids); - } - - public static function fillAttachments(&$notices) - { - $ids = self::_idsOf($notices); - $f2pMap = File_to_post::listGet('post_id', $ids); - $fileIds = []; - foreach ($f2pMap as $noticeId => $f2ps) { - foreach ($f2ps as $f2p) { - $fileIds[] = $f2p->file_id; - } - } - - $fileIds = array_unique($fileIds); - $fileMap = File::pivotGet('id', $fileIds); - foreach ($notices as $notice) { - $files = []; - $f2ps = $f2pMap[$notice->id]; - foreach ($f2ps as $f2p) { - if (!isset($fileMap[$f2p->file_id])) { - // We have probably deleted value from fileMap since - // it as a NULL entry (see the following elseif). - continue; - } elseif (is_null($fileMap[$f2p->file_id])) { - // If the file id lookup returned a NULL value, it doesn't - // exist in our file table! So this is a remnant file_to_post - // entry that is no longer valid and should be removed. - common_debug('ATTACHMENT deleting f2p for post_id='.$f2p->post_id.' file_id='.$f2p->file_id); - $f2p->delete(); - unset($fileMap[$f2p->file_id]); - continue; - } - $files[] = $fileMap[$f2p->file_id]; - } - $notice->_setAttachments($files); - } - } - - public static function fillReplies(&$notices) - { - $ids = self::_idsOf($notices); - $replyMap = Reply::listGet('notice_id', $ids); - foreach ($notices as $notice) { - $replies = $replyMap[$notice->id]; - $ids = array(); - foreach ($replies as $reply) { - $ids[] = $reply->profile_id; - } - $notice->_setReplies($ids); - } - } - - public static function beforeSchemaUpdate() - { - $table = strtolower(get_called_class()); - $schema = Schema::get(); - $schemadef = $schema->getTableDef($table); - - // 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']) - && isset($schemadef['fields']['lon']) - && isset($schemadef['fields']['location_id']) - && isset($schemadef['fields']['location_ns'])) { - // Then we make sure the Notice_location table is created! - $schema->ensureTable('notice_location', Notice_location::schemaDef()); - - // Then we continue on our road to migration! - echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)"; - - $notice = new Notice(); - $notice->query(sprintf( - 'SELECT id, lat, lon, location_id, location_ns FROM %1$s ' . - 'WHERE lat IS NOT NULL ' . - 'OR lon IS NOT NULL ' . - 'OR location_id IS NOT NULL ' . - 'OR location_ns IS NOT NULL', - common_database_tablename($table) - )); - print "\nFound {$notice->N} notices with location data, inserting"; - while ($notice->fetch()) { - $notloc = Notice_location::getKV('notice_id', $notice->id); - if ($notloc instanceof Notice_location) { - print "-"; - continue; - } - $notloc = new Notice_location(); - $notloc->notice_id = $notice->id; - $notloc->lat= $notice->lat; - $notloc->lon= $notice->lon; - $notloc->location_id= $notice->location_id; - $notloc->location_ns= $notice->location_ns; - $notloc->insert(); - print "."; - } - print "\n"; - } - - /** - * 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( - <<<'END' - UPDATE %1$s - SET %2$s = NULL, modified = CURRENT_TIMESTAMP - WHERE id IN (%3$s) - END, - $notice->escapedTableName(), - $field, - implode(',', $ids) - )); - break; - default: - throw new ServerException('The programmer sucks, invalid action name when fixing table.'); - } - printfnq("DONE.\n"); - } - unset($notice); - } - } - } - - public function delPref($namespace, $topic) - { - return Notice_prefs::setData($this, $namespace, $topic, null); - } - - public function getPref($namespace, $topic, $default = null) - { - // If you want an exception to be thrown, call Notice_prefs::getData directly - try { - return Notice_prefs::getData($this, $namespace, $topic, $default); - } catch (NoResultException $e) { - return null; - } - } - - // The same as getPref but will fall back to common_config value for the same namespace/topic - public function getConfigPref($namespace, $topic) - { - return Notice_prefs::getConfigData($this, $namespace, $topic); - } - - public function setPref($namespace, $topic, $data) - { - return Notice_prefs::setData($this, $namespace, $topic, $data); - } -} diff --git a/src/Entity/NoticeLocation.php b/src/Entity/NoticeLocation.php new file mode 100644 index 0000000000..f3a7502f5c --- /dev/null +++ b/src/Entity/NoticeLocation.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Notice's location + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class NoticeLocation +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'notice_location', + 'fields' => [ + 'notice_id' => ['type' => 'int', 'not null' => true, 'description' => 'notice that is the reply'], + 'lat' => ['type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'], + 'lon' => ['type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'], + 'location_id' => ['type' => 'int', 'description' => 'location id if possible'], + 'location_ns' => ['type' => 'int', 'description' => 'namespace for location'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['notice_id'], + 'foreign keys' => [ + 'notice_location_notice_id_fkey' => ['notice', ['notice_id' => 'id']], + ], + 'indexes' => [ + 'notice_location_location_id_idx' => ['location_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/NoticePrefs.php b/src/Entity/NoticePrefs.php new file mode 100644 index 0000000000..b4edf2bb6c --- /dev/null +++ b/src/Entity/NoticePrefs.php @@ -0,0 +1,65 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Notice preferences + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Diogo Cordeiro + * @license 2018 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class NoticePrefs +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'notice_prefs', + 'fields' => [ + 'notice_id' => ['type' => 'int', 'not null' => true, 'description' => 'user'], + 'namespace' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'], + 'topic' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'], + 'data' => ['type' => 'blob', 'description' => 'topic data, may be anything'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['notice_id', 'namespace', 'topic'], + 'foreign keys' => [ + 'notice_prefs_notice_id_fkey' => ['notice', ['notice_id' => 'id']], + ], + 'indexes' => [ + 'notice_prefs_notice_id_idx' => ['notice_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/NoticeSource.php b/src/Entity/NoticeSource.php new file mode 100644 index 0000000000..5ae316eb83 --- /dev/null +++ b/src/Entity/NoticeSource.php @@ -0,0 +1,57 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Notices sources + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class NoticeSource +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'notice_source', + 'fields' => [ + 'code' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'source code'], + 'name' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the source'], + 'url' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'url to link to'], + 'notice_id' => ['type' => 'int', 'not null' => true, 'default' => 0, 'description' => 'date this record was created'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['code'], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/NoticeTag.php b/src/Entity/NoticeTag.php new file mode 100644 index 0000000000..ac5c5b2897 --- /dev/null +++ b/src/Entity/NoticeTag.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Notice Tag + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class NoticeTag +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'notice_tag', + 'description' => 'Hash tags', + 'fields' => [ + 'tag' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this notice'], + 'notice_id' => ['type' => 'int', 'not null' => true, 'description' => 'notice tagged'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + ], + 'primary key' => ['tag', 'notice_id'], + 'foreign keys' => [ + 'notice_tag_notice_id_fkey' => ['notice', ['notice_id' => 'id']], + ], + 'indexes' => [ + 'notice_tag_created_idx' => ['created'], + 'notice_tag_notice_id_idx' => ['notice_id'], + 'notice_tag_tag_created_notice_id_idx' => ['tag', 'created', 'notice_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Notice_location.php b/src/Entity/Notice_location.php deleted file mode 100644 index dd1b2dc066..0000000000 --- a/src/Entity/Notice_location.php +++ /dev/null @@ -1,92 +0,0 @@ -. - -/** - * Table Definition for notice_location - */ - -defined('GNUSOCIAL') || die(); - -class Notice_location extends Managed_DataObject -{ - public $__table = 'notice_location'; // table name - public $notice_id; // int(4) primary_key not_null - public $lat; // decimal(10,7) - public $lon; // decimal(10,7) - public $location_id; // int(4) - public $location_ns; // int(4) - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - 'fields' => array( - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice that is the reply'), - 'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'), - 'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'), - 'location_id' => array('type' => 'int', 'description' => 'location id if possible'), - 'location_ns' => array('type' => 'int', 'description' => 'namespace for location'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('notice_id'), - 'foreign keys' => array( - 'notice_location_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - ), - 'indexes' => array( - 'notice_location_location_id_idx' => array('location_id'), - ), - ); - } - - public static function locFromStored(Notice $stored) - { - $loc = new Notice_location(); - $loc->notice_id = $stored->getID(); - if (!$loc->find(true)) { - throw new NoResultException($loc); - } - return $loc->asLocation(); - } - - public static function fromLocation(Location $location) - { - $notloc = new Notice_location(); - $notloc->lat = $location->lat; - $notloc->lon = $location->lon; - $notloc->location_ns = $location->location_ns; - $notloc->location_id = $location->location_id; - return $notloc; - } - - public function asLocation() - { - $location = null; - - if (!empty($this->location_id) && !empty($this->location_ns)) { - $location = Location::fromId($this->location_id, $this->location_ns); - } - - if (is_null($location)) { // no ID, or Location::fromId() failed - $location = Location::fromLatLon($this->lat, $this->lon); - } - - if (is_null($location)) { - throw new ServerException('Location could not be looked up from existing data.'); - } - - return $location; - } -} diff --git a/src/Entity/Notice_prefs.php b/src/Entity/Notice_prefs.php deleted file mode 100644 index 39ebe1c231..0000000000 --- a/src/Entity/Notice_prefs.php +++ /dev/null @@ -1,175 +0,0 @@ -. - -/** - * Data class for Notice preferences - * - * @category Data - * @package GNUsocial - * @author Mikael Nordfeldth - * @author Diogo Cordeiro - * @copyright 2013 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Notice_prefs extends Managed_DataObject -{ - public $__table = 'notice_prefs'; // table name - public $notice_id; // int(4) primary_key not_null - public $namespace; // varchar(191) not_null - public $topic; // varchar(191) not_null - public $data; // text - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - 'fields' => array( - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'), - 'namespace' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'), - 'topic' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'), - 'data' => array('type' => 'blob', 'description' => 'topic data, may be anything'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('notice_id', 'namespace', 'topic'), - 'foreign keys' => array( - 'notice_prefs_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - ), - ); - } - - public static function getNamespacePrefs(Notice $notice, $namespace, array $topic = []) - { - if (empty($topic)) { - $prefs = new Notice_prefs(); - $prefs->notice_id = $notice->getID(); - $prefs->namespace = $namespace; - $prefs->find(); - } else { - $prefs = self::pivotGet('notice_id', $notice->getID(), array('namespace'=>$namespace, 'topic'=>$topic)); - } - - if (empty($prefs->N)) { - throw new NoResultException($prefs); - } - - return $prefs; - } - - public static function getNamespace(Notice $notice, $namespace, array $topic = []) - { - $prefs = self::getNamespacePrefs($notice, $namespace, $topic); - return $prefs->fetchAll(); - } - - public static function getAll(Notice $notice) - { - try { - $prefs = self::listFind('notice_id', array($notice->getID())); - } catch (NoResultException $e) { - return array(); - } - - $list = array(); - while ($prefs->fetch()) { - if (!isset($list[$prefs->namespace])) { - $list[$prefs->namespace] = array(); - } - $list[$prefs->namespace][$prefs->topic] = $prefs->data; - } - return $list; - } - - public static function getTopic(Notice $notice, $namespace, $topic) - { - return self::getByPK([ - 'notice_id' => $notice->getID(), - 'namespace' => $namespace, - 'topic' => $topic, - ]); - } - - public static function getData(Notice $notice, $namespace, $topic, $def = null) - { - try { - $pref = self::getTopic($notice, $namespace, $topic); - } catch (NoResultException $e) { - if ($def === null) { - // If no default value was set, continue the exception. - throw $e; - } - // If there was a default value, return that. - return $def; - } - return $pref->data; - } - - public static function getConfigData(Notice $notice, $namespace, $topic) - { - try { - $data = self::getData($notice, $namespace, $topic); - } catch (NoResultException $e) { - $data = common_config($namespace, $topic); - } - return $data; - } - - /* - * Sets a notice preference based on Notice, namespace and topic - * - * @param Notice $notice Which notice this is for - * @param string $namespace Under which namespace (pluginname etc.) - * @param string $topic Preference name (think key in key-val store) - * @param string $data Data to be put into preference storage, null means delete - * - * @return true if changes are made, false if no action taken - * @throws ServerException if preference could not be saved - */ - public static function setData(Notice $notice, $namespace, $topic, $data = null) - { - try { - $pref = self::getTopic($notice, $namespace, $topic); - if (is_null($data)) { - $pref->delete(); - } else { - $orig = clone($pref); - $pref->data = DB_DataObject_Cast::blob($data); - $pref->update($orig); - } - return true; - } catch (NoResultException $e) { - if (is_null($data)) { - return false; // No action taken - } - } - - $pref = new Notice_prefs(); - $pref->notice_id = $notice->getID(); - $pref->namespace = $namespace; - $pref->topic = $topic; - $pref->data = DB_DataObject_Cast::blob($data); - $pref->created = common_sql_now(); - - if ($pref->insert() === false) { - throw new ServerException('Could not save notice preference.'); - } - return true; - } -} diff --git a/src/Entity/Notice_source.php b/src/Entity/Notice_source.php deleted file mode 100644 index 205c14b36d..0000000000 --- a/src/Entity/Notice_source.php +++ /dev/null @@ -1,52 +0,0 @@ -. - -/** - * Table Definition for notice_source - */ - -defined('GNUSOCIAL') || die(); - -class Notice_source extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'notice_source'; // table name - public $code; // varchar(32) primary_key not_null - public $name; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $url; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'source code'), - 'name' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the source'), - 'url' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'url to link to'), - 'notice_id' => array('type' => 'int', 'not null' => true, 'default' => 0, 'description' => 'notice id'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('code'), - ); - } -} diff --git a/src/Entity/Notice_tag.php b/src/Entity/Notice_tag.php deleted file mode 100644 index c742538f5e..0000000000 --- a/src/Entity/Notice_tag.php +++ /dev/null @@ -1,97 +0,0 @@ -. - -/* - * @copyright 2008, 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Notice_tag extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'notice_tag'; // table name - public $tag; // varchar(64) primary_key not_null - public $notice_id; // int(4) primary_key not_null - public $created; // datetime() - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'description' => 'Hash tags', - 'fields' => array( - 'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this notice'), - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice tagged'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - ), - 'primary key' => array('tag', 'notice_id'), - 'foreign keys' => array( - 'notice_tag_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - ), - 'indexes' => array( - 'notice_tag_created_idx' => array('created'), - 'notice_tag_notice_id_idx' => array('notice_id'), - 'notice_tag_tag_created_notice_id_idx' => array('tag', 'created', 'notice_id') - ), - ); - } - - public static function getStream( - $tag, - $offset = 0, - $limit = 20, - $sinceId = 0, - $maxId = 0 - ) { - // FIXME: Get the Profile::current value some other way - // to avoid confusino between queue processing and session. - $stream = new TagNoticeStream($tag, Profile::current()); - return $stream; - } - - public function blowCache($blowLast = false) - { - self::blow('notice_tag:notice_ids:%s', Cache::keyize($this->tag)); - if ($blowLast) { - self::blow('notice_tag:notice_ids:%s;last', Cache::keyize($this->tag)); - } - } - - public static function url($tag) - { - if (common_config('singleuser', 'enabled')) { - // Regular TagAction isn't set up in 1user mode - $nickname = User::singleUserNickname(); - $url = common_local_url( - 'showstream', - [ - 'nickname' => $nickname, - 'tag' => $tag, - ] - ); - } else { - $url = common_local_url('tag', ['tag' => $tag]); - } - - return $url; - } -} diff --git a/src/Entity/OauthApplication.php b/src/Entity/OauthApplication.php new file mode 100644 index 0000000000..ab44404cf6 --- /dev/null +++ b/src/Entity/OauthApplication.php @@ -0,0 +1,73 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for OAuth Application + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class OauthApplication +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'oauth_application', + 'description' => 'OAuth application registration record', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], + 'owner' => ['type' => 'int', 'not null' => true, 'description' => 'owner of the application'], + 'consumer_key' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'application consumer key'], + 'name' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the application'], + 'description' => ['type' => 'varchar', 'length' => 191, 'description' => 'description of the application'], + 'icon' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'default' => '/theme/base/default-avatar-stream.png', 'description' => 'application icon'], + 'source_url' => ['type' => 'varchar', 'length' => 191, 'description' => 'application homepage - used for source link'], + 'organization' => ['type' => 'varchar', 'length' => 191, 'description' => 'name of the organization running the application'], + 'homepage' => ['type' => 'varchar', 'length' => 191, 'description' => 'homepage for the organization'], + 'callback_url' => ['type' => 'varchar', 'length' => 191, 'description' => 'url to redirect to after authentication'], + 'type' => ['type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'type of app, 1 = browser, 2 = desktop'], + 'access_type' => ['type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'default access type, bit 1 = read, bit 2 = write'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'oauth_application_name_key' => ['name'], // in the long run, we should perhaps not force these unique, and use another source id + ], + 'foreign keys' => [ + 'oauth_application_owner_fkey' => ['profile', ['owner' => 'id']], // Are remote users allowed to create oauth application records? + 'oauth_application_consumer_key_fkey' => ['consumer', ['consumer_key' => 'consumer_key']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/OauthApplicationUser.php b/src/Entity/OauthApplicationUser.php new file mode 100644 index 0000000000..d576d9cb87 --- /dev/null +++ b/src/Entity/OauthApplicationUser.php @@ -0,0 +1,61 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for OAuth Application User + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class OauthApplicationUser +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'oauth_application_user', + 'fields' => [ + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'user of the application'], + 'application_id' => ['type' => 'int', 'not null' => true, 'description' => 'id of the application'], + 'access_type' => ['type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'access type, bit 1 = read, bit 2 = write'], + 'token' => ['type' => 'varchar', 'length' => 191, 'description' => 'request or access token'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['profile_id', 'application_id'], + 'foreign keys' => [ + 'oauth_application_user_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + 'oauth_application_user_application_id_fkey' => ['oauth_application', ['application_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/OauthTokenAssociation.php b/src/Entity/OauthTokenAssociation.php new file mode 100644 index 0000000000..9987c143c4 --- /dev/null +++ b/src/Entity/OauthTokenAssociation.php @@ -0,0 +1,61 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for association between OAuth and internal token + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class OauthTokenAssociation +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'oauth_token_association', + 'description' => 'Associate an application ID and profile ID with an OAuth token', + 'fields' => [ + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'associated user'], + 'application_id' => ['type' => 'int', 'not null' => true, 'description' => 'the application'], + 'token' => ['type' => 'varchar', 'length' => '191', 'not null' => true, 'description' => 'token used for this association'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['profile_id', 'application_id', 'token'], + 'foreign keys' => [ + 'oauth_token_association_profile_fkey' => ['profile', ['profile_id' => 'id']], + 'oauth_token_association_application_fkey' => ['oauth_application', ['application_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Oauth_application.php b/src/Entity/Oauth_application.php deleted file mode 100644 index aa43038806..0000000000 --- a/src/Entity/Oauth_application.php +++ /dev/null @@ -1,209 +0,0 @@ -. - -/** - * Table Definition for oauth_application - */ - -defined('GNUSOCIAL') || die(); - -class Oauth_application extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'oauth_application'; // table name - public $id; // int(4) primary_key not_null - public $owner; // int(4) not_null - public $consumer_key; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $name; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $description; // varchar(191) not 255 because utf8mb4 takes more space - public $icon; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $source_url; // varchar(191) not 255 because utf8mb4 takes more space - public $organization; // varchar(191) not 255 because utf8mb4 takes more space - public $homepage; // varchar(191) not 255 because utf8mb4 takes more space - public $callback_url; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $type; // tinyint(1) - public $access_type; // tinyint(1) - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - // Bit flags - public static $readAccess = 1; - public static $writeAccess = 2; - - public static $browser = 1; - public static $desktop = 2; - - public function getConsumer() - { - return Consumer::getKV('consumer_key', $this->consumer_key); - } - - public static function maxDesc() - { - // This used to default to textlimit or allow unlimited descriptions, - // but this isn't part of a notice and the field's limited to 191 chars - // in the DB, so those seem silly. (utf8mb4 takes up more space, so can't use 255) - // - // Now just defaulting to 191 max unless a smaller application desclimit - // is actually set. Setting to 0 will use the maximum. - $max = 191; - $desclimit = intval(common_config('application', 'desclimit')); - if ($desclimit > 0 && $desclimit < $max) { - return $desclimit; - } else { - return $max; - } - } - - public static function descriptionTooLong($desc) - { - $desclimit = self::maxDesc(); - return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit)); - } - - public function setAccessFlags($read, $write) - { - if ($read) { - $this->access_type |= self::$readAccess; - } else { - $this->access_type &= ~self::$readAccess; - } - - if ($write) { - $this->access_type |= self::$writeAccess; - } else { - $this->access_type &= ~self::$writeAccess; - } - } - - public function setOriginal($filename) - { - $imagefile = new ImageFile(null, Avatar::path($filename)); - - // XXX: Do we want to have a bunch of different size icons? homepage, stream, mini? - // or just one and control size via CSS? --Zach - - $orig = clone($this); - $this->icon = Avatar::url($filename); - common_debug(common_log_objstring($this)); - return $this->update($orig); - } - - public static function getByConsumerKey($key) - { - if (empty($key)) { - return null; - } - - $app = new Oauth_application(); - $app->consumer_key = $key; - $app->limit(1); - $result = $app->find(true); - - return empty($result) ? null : $app; - } - - /** - * Handle an image upload - * - * Does all the magic for handling an image upload, and crops the - * image by default. - * - * @return void - */ - public function uploadLogo() - { - if ($_FILES['app_icon']['error'] == UPLOAD_ERR_OK) { - try { - $imagefile = ImageFile::fromUpload('app_icon'); - } catch (Exception $e) { - common_debug("damn that sucks"); - $this->showForm($e->getMessage()); - return; - } - - $filename = Avatar::filename( - $this->id, - image_type_to_extension($imagefile->type), - null, - 'oauth-app-icon-' . common_timestamp() - ); - - $filepath = Avatar::path($filename); - - move_uploaded_file($imagefile->filepath, $filepath); - - $this->setOriginal($filename); - } - } - - public function delete($useWhere = false) - { - $this->deleteAppUsers(); - - $consumer = $this->getConsumer(); - $consumer->delete(); - - return parent::delete($useWhere); - } - - private function deleteAppUsers() - { - $oauser = new Oauth_application_user(); - $oauser->application_id = $this->id; - $oauser->delete(); - } - - public static function schemaDef() - { - return array( - 'description' => 'OAuth application registration record', - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), - 'owner' => array('type' => 'int', 'not null' => true, 'description' => 'owner of the application'), - 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'application consumer key'), - 'name' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the application'), - 'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description of the application'), - 'icon' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'default' => '/theme/base/default-avatar-stream.png', 'description' => 'application icon'), - 'source_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'application homepage - used for source link'), - 'organization' => array('type' => 'varchar', 'length' => 191, 'description' => 'name of the organization running the application'), - 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'homepage for the organization'), - 'callback_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'url to redirect to after authentication'), - 'type' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'type of app, 1 = browser, 2 = desktop'), - 'access_type' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'default access type, bit 1 = read, bit 2 = write'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'oauth_application_name_key' => array('name'), // in the long run, we should perhaps not force these unique, and use another source id - ), - 'foreign keys' => array( - 'oauth_application_owner_fkey' => array('profile', array('owner' => 'id')), // Are remote users allowed to create oauth application records? - 'oauth_application_consumer_key_fkey' => array('consumer', array('consumer_key' => 'consumer_key')), - ), - 'indexes' => array( - 'oauth_application_owner_idx' => array('owner'), - 'oauth_application_consumer_key_idx' => array('consumer_key'), - ), - ); - } -} diff --git a/src/Entity/Oauth_application_user.php b/src/Entity/Oauth_application_user.php deleted file mode 100644 index 2ca199683d..0000000000 --- a/src/Entity/Oauth_application_user.php +++ /dev/null @@ -1,107 +0,0 @@ -. - -/** - * Table Definition for oauth_application_user - */ - -defined('GNUSOCIAL') || die(); - -class Oauth_application_user extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'oauth_application_user'; // table name - public $profile_id; // int(4) primary_key not_null - public $application_id; // int(4) primary_key not_null - public $access_type; // tinyint(1) - public $token; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'user of the application'), - 'application_id' => array('type' => 'int', 'not null' => true, 'description' => 'id of the application'), - 'access_type' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'access type, bit 1 = read, bit 2 = write'), - 'token' => array('type' => 'varchar', 'length' => 191, 'description' => 'request or access token'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('profile_id', 'application_id'), - 'foreign keys' => array( - 'oauth_application_user_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - 'oauth_application_user_application_id_fkey' => array('oauth_application', array('application_id' => 'id')), - ), - 'indexes' => array( - 'oauth_application_user_application_id_idx' => array('application_id'), - ), - ); - } - - public static function getByUserAndToken($user, $token) - { - if (empty($user) || empty($token)) { - return null; - } - - $oau = new Oauth_application_user(); - - $oau->profile_id = $user->id; - $oau->token = $token; - $oau->limit(1); - - $result = $oau->find(true); - - return empty($result) ? null : $oau; - } - - public function updateKeys(&$orig) - { - $this->_connect(); - $parts = array(); - foreach (array('profile_id', 'application_id', 'token', 'access_type') as $k) { - if (strcmp($this->$k, $orig->$k) != 0) { - $parts[] = $k . ' = ' . $this->_quote($this->$k); - } - } - if (count($parts) == 0) { - // No changes - return true; - } - $toupdate = implode(', ', $parts); - $toupdate .= ', modified = CURRENT_TIMESTAMP'; - - $table = $this->tableName(); - $tableName = $this->escapedTableName(); - $qry = 'UPDATE ' . $tableName . ' SET ' . $toupdate . - ' WHERE profile_id = ' . $orig->profile_id . - ' AND application_id = ' . $orig->application_id . - " AND token = '" . $orig->token . "'"; - $orig->decache(); - $result = $this->query($qry); - if ($result) { - $this->encache(); - } - return $result; - } -} diff --git a/src/Entity/Oauth_token_association.php b/src/Entity/Oauth_token_association.php deleted file mode 100644 index 02191d8c10..0000000000 --- a/src/Entity/Oauth_token_association.php +++ /dev/null @@ -1,76 +0,0 @@ -. - -/** - * Table Definition for oauth_association - */ - -defined('GNUSOCIAL') || die(); - -class Oauth_token_association extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'oauth_token_association'; // table name - public $profile_id; // int(4) primary_key not_null - public $application_id; // int(4) primary_key not_null - public $token; // varchar(191) primary key not null not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function getByUserAndToken($user, $token) - { - if (empty($user) || empty($token)) { - return null; - } - - $oau = new oauth_request_token(); - - $oau->profile_id = $user->id; - $oau->token = $token; - $oau->limit(1); - - $result = $oau->find(true); - - return empty($result) ? null : $oau; - } - - public static function schemaDef() - { - return array( - 'description' => 'Associate an application ID and profile ID with an OAuth token', - 'fields' => array( - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'associated user'), - 'application_id' => array('type' => 'int', 'not null' => true, 'description' => 'the application'), - 'token' => array('type' => 'varchar', 'length' => '191', 'not null' => true, 'description' => 'token used for this association'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('profile_id', 'application_id', 'token'), - 'foreign keys' => array( - 'oauth_token_association_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - 'oauth_token_association_application_id_fkey' => array('oauth_application', array('application_id' => 'id')), - ), - 'indexes' => array( - 'oauth_token_association_application_id_idx' => array('application_id'), - ), - ); - } -} diff --git a/src/Entity/OldSchoolPrefs.php b/src/Entity/OldSchoolPrefs.php new file mode 100644 index 0000000000..c543f1c315 --- /dev/null +++ b/src/Entity/OldSchoolPrefs.php @@ -0,0 +1,66 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Separate table for storing UI preferences + * + * @category DB + * @package GNUsocial + * + * @deprecated + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class OldSchoolPrefs +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'old_school_prefs', + 'fields' => [ + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'user who has the preference'], + 'stream_mode_only' => ['type' => 'bool', + 'default' => true, + 'description' => 'No conversation streams', ], + 'conversation_tree' => ['type' => 'bool', + 'default' => true, + 'description' => 'Hierarchical tree view for conversations', ], + 'stream_nicknames' => ['type' => 'bool', + 'default' => true, + 'description' => 'Show nicknames for authors and addressees in streams', ], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['user_id'], + 'foreign keys' => [ + 'old_school_prefs_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Old_school_prefs.php b/src/Entity/Old_school_prefs.php deleted file mode 100644 index cf62acd752..0000000000 --- a/src/Entity/Old_school_prefs.php +++ /dev/null @@ -1,69 +0,0 @@ -. - -/** - * Older-style UI preferences - * - * @category UI - * @package GNUsocial - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -/** - * Separate table for storing UI preferences - * - * @copyright 2011 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -class Old_school_prefs extends Managed_DataObject -{ - public $__table = 'old_school_prefs'; // table name - public $user_id; - public $stream_mode_only; - public $conversation_tree; - public $stream_nicknames; - public $created; - public $modified; - - public static function schemaDef() - { - return array( - 'fields' => array( - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who has the preference'), - 'stream_mode_only' => array('type' => 'bool', - 'default' => true, - 'description' => 'No conversation streams'), - 'conversation_tree' => array('type' => 'bool', - 'default' => true, - 'description' => 'Hierarchical tree view for conversations'), - 'stream_nicknames' => array('type' => 'bool', - 'default' => true, - 'description' => 'Show nicknames for authors and addressees in streams'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('user_id'), - 'foreign keys' => array( - 'old_school_prefs_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - ); - } -} diff --git a/src/Entity/Profile.php b/src/Entity/Profile.php deleted file mode 100644 index 34823c26d8..0000000000 --- a/src/Entity/Profile.php +++ /dev/null @@ -1,1871 +0,0 @@ -. - -/** - * Table Definition for profile - * - * @copyright 2008-2011 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Profile extends Managed_DataObject -{ - public $__table = 'profile'; // table name - public $id; // int(4) primary_key not_null - public $nickname; // varchar(64) multiple_key not_null - public $fullname; // text() - public $profileurl; // text() - public $homepage; // text() - public $bio; // text() multiple_key - public $location; // text() - public $lat; // decimal(10,7) - public $lon; // decimal(10,7) - public $location_id; // int(4) - public $location_ns; // int(4) - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - $def = array( - 'description' => 'local and remote users have profiles', - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), - 'nickname' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'nickname or username', 'collate' => 'utf8_general_ci'), - 'fullname' => array('type' => 'text', 'description' => 'display name', 'collate' => 'utf8_general_ci'), - 'profileurl' => array('type' => 'text', 'description' => 'URL, cached so we dont regenerate'), - 'homepage' => array('type' => 'text', 'description' => 'identifying URL', 'collate' => 'utf8_general_ci'), - 'bio' => array('type' => 'text', 'description' => 'descriptive biography', 'collate' => 'utf8_general_ci'), - 'location' => array('type' => 'text', 'description' => 'physical location', 'collate' => 'utf8_general_ci'), - 'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'), - 'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'), - 'location_id' => array('type' => 'int', 'description' => 'location id if possible'), - 'location_ns' => array('type' => 'int', 'description' => 'namespace for location'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - 'indexes' => array( - 'profile_nickname_created_id_idx' => array('nickname', 'created', 'id'), - ), - 'fulltext indexes' => array( - 'profile_fulltext_idx' => array('nickname', 'fullname', 'location', 'bio', 'homepage'), - ), - ); - - return $def; - } - - public static function getByEmail($email) - { - // in the future, profiles should have emails stored... - $user = User::getKV('email', $email); - if (!($user instanceof User)) { - throw new NoSuchUserException(array('email'=>$email)); - } - return $user->getProfile(); - } - - protected $_user = array(); - - public function getUser() - { - if (!isset($this->_user[$this->id])) { - $cur_user = common_current_user(); - if (($cur_user instanceof User) && $cur_user->sameAs($this)) { - $user = $cur_user; - } else { - $user = User::getKV('id', $this->id); - if (!$user instanceof User) { - throw new NoSuchUserException(array('id'=>$this->id)); - } - } - $this->_user[$this->id] = $user; - } - return $this->_user[$this->id]; - } - - protected $_group = array(); - - public function getGroup() - { - if (!isset($this->_group[$this->id])) { - $group = User_group::getKV('profile_id', $this->id); - if (!$group instanceof User_group) { - throw new NoSuchGroupException(array('profile_id'=>$this->id)); - } - $this->_group[$this->id] = $group; - } - return $this->_group[$this->id]; - } - - public function isGroup() - { - try { - $this->getGroup(); - return true; - } catch (NoSuchGroupException $e) { - return false; - } - } - - public function isPerson() - { - // Maybe other things than PERSON and GROUP can have Profiles in the future? - return !$this->isGroup(); - } - - public function isLocal() - { - try { - $this->getUser(); - } catch (NoSuchUserException $e) { - return false; - } - return true; - } - - // Returns false if the user has no password (which will always - // be the case for remote users). This can be the case for OpenID - // logins or other mechanisms which don't store a password hash. - public function hasPassword() - { - try { - return $this->getUser()->hasPassword(); - } catch (NoSuchUserException $e) { - return false; - } - } - - public function getObjectType() - { - // FIXME: More types... like peopletags and whatever - if ($this->isGroup()) { - return ActivityObject::GROUP; - } else { - return ActivityObject::PERSON; - } - } - - public function getAvatar($width, $height=null) - { - return Avatar::byProfile($this, $width, $height); - } - - public function setOriginal($filename) - { - if ($this->isGroup()) { - // Until Group avatars are handled just like profile avatars. - return $this->getGroup()->setOriginal($filename); - } - - $imagefile = new ImageFile(null, Avatar::path($filename)); - - $avatar = new Avatar(); - $avatar->profile_id = $this->id; - $avatar->width = $imagefile->width; - $avatar->height = $imagefile->height; - $avatar->mediatype = image_type_to_mime_type($imagefile->type); - $avatar->filename = $filename; - $avatar->original = true; - $avatar->created = common_sql_now(); - - // XXX: start a transaction here - if (!Avatar::deleteFromProfile($this, true) || !$avatar->insert()) { - // If we can't delete the old avatars, let's abort right here. - @unlink(Avatar::path($filename)); - return null; - } - - return $avatar; - } - - /** - * Gets either the full name (if filled) or the nickname. - * - * @return string - */ - public function getBestName() - { - return ($this->fullname) ? $this->fullname : $this->nickname; - } - - /** - * Takes the currently scoped profile into account to give a name - * to list in notice streams. Preferences may differ between profiles. - */ - public function getStreamName() - { - $user = common_current_user(); - if ($user instanceof User && $user->streamNicknames()) { - return $this->nickname; - } - - return $this->getBestName(); - } - - /** - * Gets the full name (if filled) with acct URI, URL, or URI as a - * parenthetical (in that order, for each not found). If no full - * name is found only the second part is returned, without ()s. - * - * @return string - */ - public function getFancyName() - { - $uri = null; - try { - $uri = $this->getAcctUri(false); - } catch (ProfileNoAcctUriException $e) { - try { - $uri = $this->getUrl(); - } catch (InvalidUrlException $e) { - $uri = $this->getUri(); - } - } - - if (mb_strlen($this->getFullname()) > 0) { - // TRANS: The "fancy name": Full name of a profile or group (%1$s) followed by some URI (%2$s) in parentheses. - return sprintf(_m('FANCYNAME', '%1$s (%2$s)'), $this->getFullname(), $uri); - } else { - return $uri; - } - } - - /** - * Get the most recent notice posted by this user, if any. - * - * @return mixed Notice or null - */ - public function getCurrentNotice(Profile $scoped = null) - { - try { - $notice = $this->getNotices(0, 1, 0, 0, $scoped); - - if ($notice->fetch()) { - if ($notice instanceof ArrayWrapper) { - // hack for things trying to work with single notices - // ...but this shouldn't happen anymore I think. Keeping it for safety... - return $notice->_items[0]; - } - return $notice; - } - } catch (PrivateStreamException $e) { - // Maybe we should let this through if it's handled well upstream - return null; - } - - return null; - } - - public function getReplies($offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0) - { - return Reply::stream($this->getID(), $offset, $limit, $since_id, $before_id); - } - - public function getTaggedNotices($tag, $offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $max_id = 0) - { - //FIXME: Get Profile::current() some other way to avoid possible - // confusion between current session profile and background processing. - $stream = new TaggedProfileNoticeStream($this, $tag, Profile::current()); - - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - public function getNotices($offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $max_id = 0, Profile $scoped = null) - { - $stream = new ProfileNoticeStream($this, $scoped); - - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - public function isMember(User_group $group) - { - $groups = $this->getGroups(0, null); - while ($groups instanceof User_group && $groups->fetch()) { - if ($groups->id == $group->id) { - return true; - } - } - return false; - } - - public function isAdmin(User_group $group) - { - $gm = Group_member::pkeyGet(array('profile_id' => $this->id, - 'group_id' => $group->id)); - return (!empty($gm) && $gm->is_admin); - } - - public function isPendingMember($group) - { - $request = Group_join_queue::pkeyGet(array('profile_id' => $this->id, - 'group_id' => $group->id)); - return !empty($request); - } - - public function getGroups($offset = 0, $limit = PROFILES_PER_PAGE) - { - $ids = array(); - - $keypart = sprintf('profile:groups:%d', $this->id); - - $idstring = self::cacheGet($keypart); - - if ($idstring !== false) { - $ids = explode(',', $idstring); - } else { - $gm = new Group_member(); - - $gm->profile_id = $this->id; - $gm->orderBy('created DESC, group_id DESC'); - - if ($gm->find()) { - while ($gm->fetch()) { - $ids[] = $gm->group_id; - } - } - - self::cacheSet($keypart, implode(',', $ids)); - } - - if (!is_null($offset) && !is_null($limit)) { - $ids = array_slice($ids, $offset, $limit); - } - - try { - return User_group::multiGet('id', $ids); - } catch (NoResultException $e) { - return null; // throw exception when we handle it everywhere - } - } - - public function getGroupCount() - { - $groups = $this->getGroups(0, null); - return $groups instanceof User_group - ? $groups->N - : 0; - } - - public function isTagged($peopletag) - { - $tag = Profile_tag::pkeyGet(array('tagger' => $peopletag->tagger, - 'tagged' => $this->id, - 'tag' => $peopletag->tag)); - return !empty($tag); - } - - public function canTag($tagged) - { - if (empty($tagged)) { - return false; - } - - if ($tagged->id == $this->id) { - return true; - } - - $all = common_config('peopletag', 'allow_tagging', 'all'); - $local = common_config('peopletag', 'allow_tagging', 'local'); - $remote = common_config('peopletag', 'allow_tagging', 'remote'); - $subs = common_config('peopletag', 'allow_tagging', 'subs'); - - if ($all) { - return true; - } - - $tagged_user = $tagged->getUser(); - if (!empty($tagged_user)) { - if ($local) { - return true; - } - } elseif ($subs) { - return (Subscription::exists($this, $tagged) || - Subscription::exists($tagged, $this)); - } elseif ($remote) { - return true; - } - return false; - } - - public function getLists(Profile $scoped = null, $offset = 0, $limit = null, $since_id = 0, $max_id = 0) - { - $ids = array(); - - $keypart = sprintf('profile:lists:%d', $this->id); - - $idstr = self::cacheGet($keypart); - - if ($idstr !== false) { - $ids = explode(',', $idstr); - } else { - $list = new Profile_list(); - $list->selectAdd(); - $list->selectAdd('id'); - $list->tagger = $this->id; - $list->selectAdd('id as "cursor"'); - - if ($since_id > 0) { - $list->whereAdd('id > ' . $since_id); - } - - if ($max_id > 0) { - $list->whereAdd('id <= ' . $max_id); - } - - if ($offset >= 0 && !is_null($limit)) { - $list->limit($offset, $limit); - } - - $list->orderBy('id DESC'); - - if ($list->find()) { - while ($list->fetch()) { - $ids[] = $list->id; - } - } - - self::cacheSet($keypart, implode(',', $ids)); - } - - $showPrivate = $this->sameAs($scoped); - - $lists = array(); - - foreach ($ids as $id) { - $list = Profile_list::getKV('id', $id); - if (!empty($list) && - ($showPrivate || !$list->private)) { - if (!isset($list->cursor)) { - $list->cursor = $list->id; - } - - $lists[] = $list; - } - } - - return new ArrayWrapper($lists); - } - - /** - * Get tags that other people put on this profile, in reverse-chron order - * - * @param Profile $scoped User we are requesting as - * @param int $offset Offset from latest - * @param int $limit Max number to get - * @param datetime $since_id max date - * @param datetime $max_id min date - * - * @return Profile_list resulting lists - */ - - public function getOtherTags(Profile $scoped = null, int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) - { - $list = new Profile_list(); - - if (common_config('db', 'type') !== 'mysql') { - $cursor = sprintf( - '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . - 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', - "FROM (profile_tag.modified - TIMESTAMP '1970-01-01 00:00:00')" - ); - } else { - // The SQL/Foundation conforming implementation above doesn't work on MariaDB/MySQL - $cursor = "timestampdiff(SECOND, '1970-01-01', profile_tag.modified) AS `cursor`"; - } - - $qry = sprintf( - 'SELECT profile_list.*, ' . $cursor . ' ' . - 'FROM profile_tag INNER JOIN profile_list ' . - 'ON (profile_tag.tagger = profile_list.tagger ' . - ' AND profile_tag.tag = profile_list.tag) ' . - 'WHERE profile_tag.tagged = %d ', - $this->id - ); - - if (!is_null($scoped)) { - $qry .= sprintf( - 'AND ( profile_list.private IS NOT TRUE ' . - 'OR ( profile_list.tagger = %d AND ' . - 'profile_list.private IS TRUE ) )', - $scoped->getID() - ); - } else { - $qry .= 'AND profile_list.private IS NOT TRUE '; - } - - if ($since > 0) { - $qry .= 'AND cursor > ' . $since . ' '; - } - - if ($upto > 0) { - $qry .= 'AND cursor < ' . $upto . ' '; - } - - $qry .= 'ORDER BY profile_tag.modified DESC, profile_tag.tagged DESC '; - - if ($offset >= 0 && !is_null($limit)) { - $qry .= sprintf('LIMIT %d OFFSET %d ', $limit, $offset); - } - - $list->query($qry); - return $list; - } - - public function getPrivateTags($offset = 0, $limit = null, $since_id = 0, $max_id = 0) - { - $tags = new Profile_list(); - $tags->private = true; - $tags->tagger = $this->id; - - if ($since_id > 0) { - $tags->whereAdd('id > ' . $since_id); - } - - if ($max_id > 0) { - $tags->whereAdd('id <= ' . $max_id); - } - - if ($offset >= 0 && !is_null($limit)) { - $tags->limit($offset, $limit); - } - - $tags->orderBy('id DESC'); - $tags->find(); - - return $tags; - } - - public function hasLocalTags() - { - $tags = new Profile_tag(); - - $tags->joinAdd(array('tagger', 'user:id')); - $tags->whereAdd('tagged = ' . $this->id); - $tags->whereAdd('tagger <> ' . $this->id); - - $tags->limit(0, 1); - $tags->fetch(); - - return ($tags->N == 0) ? false : true; - } - - public function getTagSubscriptions(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) - { - $lists = new Profile_list(); - - $lists->joinAdd(['id', 'profile_tag_subscription:profile_tag_id']); - - if (common_config('db', 'type') !== 'mysql') { - $lists->selectAdd(sprintf( - '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . - 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', - "FROM (profile_tag_subscription.created - TIMESTAMP '1970-01-01 00:00:00')" - )); - } else { - $lists->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag_subscription.created) AS `cursor`"); - } - - $lists->whereAdd('profile_tag_subscription.profile_id = '.$this->id); - - if ($since > 0) { - $lists->whereAdd('cursor > ' . $since); - } - - if ($upto > 0) { - $lists->whereAdd('cursor <= ' . $upto); - } - - if ($offset >= 0 && !is_null($limit)) { - $lists->limit($offset, $limit); - } - - $lists->orderBy('profile_tag_subscription.created DESC, profile_list.id DESC'); - $lists->find(); - - return $lists; - } - - /** - * Request to join the given group. - * May throw exceptions on failure. - * - * @param User_group $group - * @return mixed: Group_member on success, Group_join_queue if pending approval, null on some cancels? - */ - public function joinGroup(User_group $group) - { - $join = null; - if ($group->join_policy == User_group::JOIN_POLICY_MODERATE) { - $join = Group_join_queue::saveNew($this, $group); - } else { - if (Event::handle('StartJoinGroup', array($group, $this))) { - $join = Group_member::join($group->id, $this->id); - self::blow('profile:groups:%d', $this->id); - self::blow('group:member_ids:%d', $group->id); - self::blow('group:member_count:%d', $group->id); - Event::handle('EndJoinGroup', array($group, $this)); - } - } - if ($join) { - // Send any applicable notifications... - $join->notify(); - } - return $join; - } - - /** - * Leave a group that this profile is a member of. - * - * @param User_group $group - */ - public function leaveGroup(User_group $group) - { - if (Event::handle('StartLeaveGroup', array($group, $this))) { - Group_member::leave($group->id, $this->id); - self::blow('profile:groups:%d', $this->id); - self::blow('group:member_ids:%d', $group->id); - self::blow('group:member_count:%d', $group->id); - Event::handle('EndLeaveGroup', array($group, $this)); - } - } - - public function avatarUrl($size = AVATAR_PROFILE_SIZE) - { - return Avatar::urlByProfile($this, $size); - } - - public function getSubscribed($offset = 0, $limit = null) - { - $subs = Subscription::getSubscribedIDs($this->id, $offset, $limit); - try { - $profiles = Profile::multiGet('id', $subs); - } catch (NoResultException $e) { - return $e->obj; - } - return $profiles; - } - - public function getSubscribers($offset = 0, $limit = null) - { - $subs = Subscription::getSubscriberIDs($this->id, $offset, $limit); - try { - $profiles = Profile::multiGet('id', $subs); - } catch (NoResultException $e) { - return $e->obj; - } - return $profiles; - } - - public function getTaggedSubscribers($tag, $offset = 0, $limit = null) - { - $profile = new Profile(); - - $qry = <<getID()} - AND profile_tag.tag = '{$profile->escape($tag)}' - AND subscription.subscribed <> subscription.subscriber - ORDER BY subscription.created DESC, subscription.subscriber DESC - END; - - if ($offset) { - $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } - - $cnt = $profile->query($qry); - - return $profile; - } - - public function getTaggedSubscriptions($tag, $offset = 0, $limit = null) - { - $profile = new Profile(); - - $qry = <<getID()} - AND profile_tag.tag = '{$profile->escape($tag)}' - AND subscription.subscribed <> subscription.subscriber - ORDER BY subscription.created DESC, subscription.subscribed DESC - END; - - if ($offset) { - $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; - } - - $profile->query($qry); - - return $profile; - } - - /** - * Get pending subscribers, who have not yet been approved. - * - * @param int $offset - * @param int $limit - * @return Profile - */ - public function getRequests($offset = 0, $limit = null) - { - // FIXME: mysql only - $subqueue = new Profile(); - $subqueue->joinAdd(array('id', 'subscription_queue:subscriber')); - $subqueue->whereAdd(sprintf('subscription_queue.subscribed = %d', $this->getID())); - $subqueue->limit($offset, $limit); - $subqueue->orderBy( - 'subscription_queue.created DESC, subscription_queue.subscriber DESC' - ); - if (!$subqueue->find()) { - throw new NoResultException($subqueue); - } - return $subqueue; - } - - public function subscriptionCount() - { - $c = Cache::instance(); - - if (!empty($c)) { - $cnt = $c->get(Cache::key('profile:subscription_count:'.$this->id)); - if (is_integer($cnt)) { - return (int) $cnt; - } - } - - $sub = new Subscription(); - $sub->subscriber = $this->id; - - $cnt = (int) $sub->count('distinct subscribed'); - - // Local users are subscribed to themselves - if ($this->isLocal()) { - $cnt = ($cnt > 0) ? $cnt - 1 : $cnt; - } - - if (!empty($c)) { - $c->set(Cache::key('profile:subscription_count:'.$this->id), $cnt); - } - - return $cnt; - } - - public function subscriberCount() - { - $c = Cache::instance(); - if (!empty($c)) { - $cnt = $c->get(Cache::key('profile:subscriber_count:'.$this->id)); - if (is_integer($cnt)) { - return (int) $cnt; - } - } - - $sub = new Subscription(); - $sub->subscribed = $this->id; - $sub->whereAdd('subscriber <> subscribed'); - $cnt = (int) $sub->count('DISTINCT subscriber'); - - if (!empty($c)) { - $c->set(Cache::key('profile:subscriber_count:'.$this->id), $cnt); - } - - return $cnt; - } - - /** - * Is this profile subscribed to another profile? - * - * @param Profile $other - * @return boolean - */ - public function isSubscribed(Profile $other) - { - return Subscription::exists($this, $other); - } - - public function readableBy(Profile $other = null) - { - // If it's not a private stream, it's readable by anyone - if (!$this->isPrivateStream()) { - return true; - } - - // If it's a private stream, $other must be a subscriber to $this - return is_null($other) ? false : $other->isSubscribed($this); - } - - public function requiresSubscriptionApproval(Profile $other = null): bool - { - if (!$this->isLocal()) { - // We don't know for remote users, and we'll always be able to send - // the request. Whether it'll work immediately or require moderation - // can be determined in another function. - return false; - } - - // Assume that profiles _we_ subscribe to are permitted. Could be made configurable. - if (!is_null($other) && $this->isSubscribed($other)) { - return false; - } - - // If the local user either has a private stream (implies the following) - // or user has a moderation policy for new subscriptions, return true. - return $this->isPrivateStream() || $this->getUser()->subscribe_policy === User::SUBSCRIBE_POLICY_MODERATE; - } - - /** - * Check if a pending subscription request is outstanding for this... - * - * @param Profile $other - * @return boolean - */ - public function hasPendingSubscription(Profile $other) - { - return Subscription_queue::exists($this, $other); - } - - /** - * Are these two profiles subscribed to each other? - * - * @param Profile $other - * @return boolean - */ - public function mutuallySubscribed(Profile $other) - { - return $this->isSubscribed($other) && - $other->isSubscribed($this); - } - - public function noticeCount() - { - $c = Cache::instance(); - - if (!empty($c)) { - $cnt = $c->get(Cache::key('profile:notice_count:'.$this->getID())); - if (is_integer($cnt)) { - return (int) $cnt; - } - } - - $notices = new Notice(); - $notices->profile_id = $this->getID(); - $notices->verb = ActivityVerb::POST; - $cnt = (int) $notices->count('id'); // Not sure if I imagine this, but 'id' was faster than the defaulting 'uri'? - - if (!empty($c)) { - $c->set(Cache::key('profile:notice_count:'.$this->getID()), $cnt); - } - - return $cnt; - } - - public function blowSubscriberCount() - { - $c = Cache::instance(); - if (!empty($c)) { - $c->delete(Cache::key('profile:subscriber_count:'.$this->id)); - } - } - - public function blowSubscriptionCount() - { - $c = Cache::instance(); - if (!empty($c)) { - $c->delete(Cache::key('profile:subscription_count:'.$this->id)); - } - } - - public function blowNoticeCount() - { - $c = Cache::instance(); - if (!empty($c)) { - $c->delete(Cache::key('profile:notice_count:'.$this->id)); - } - } - - public static function maxBio() - { - $biolimit = common_config('profile', 'biolimit'); - // null => use global limit (distinct from 0!) - if (is_null($biolimit)) { - $biolimit = common_config('site', 'textlimit'); - } - return $biolimit; - } - - public static function bioTooLong($bio) - { - $biolimit = self::maxBio(); - return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit)); - } - - public function update($dataObject = false) - { - if (is_object($dataObject) && $this->nickname != $dataObject->nickname) { - try { - $local = $this->getUser(); - common_debug("Updating User ({$this->id}) nickname from {$dataObject->nickname} to {$this->nickname}"); - $origuser = clone($local); - $local->nickname = $this->nickname; - // updateWithKeys throws exception on failure. - $local->updateWithKeys($origuser); - - // Clear the site owner, in case nickname changed - if ($local->hasRole(Profile_role::OWNER)) { - User::blow('user:site_owner'); - } - } catch (NoSuchUserException $e) { - // Nevermind... - } - } - - return parent::update($dataObject); - } - - public function getRelSelf() - { - return ['href' => $this->getUrl(), - 'text' => common_config('site', 'name'), - 'image' => Avatar::urlByProfile($this)]; - } - - // All the known rel="me", used for the IndieWeb audience - public function getRelMes() - { - $relMes = array(); - try { - $relMes[] = $this->getRelSelf(); - } catch (InvalidUrlException $e) { - // no valid profile URL available - } - if (common_valid_http_url($this->getHomepage())) { - $relMes[] = ['href' => $this->getHomepage(), - 'text' => _('Homepage'), - 'image' => null]; - } - Event::handle('OtherAccountProfiles', array($this, &$relMes)); - return $relMes; - } - - public function delete($useWhere = false) - { - $this->_deleteNotices(); - $this->_deleteSubscriptions(); - $this->_deleteTags(); - $this->_deleteBlocks(); - $this->_deleteAttentions(); - Avatar::deleteFromProfile($this, true); - - $this->grantRole(Profile_role::DELETED); - - $localuser = User::getKV('id', $this->id); - if ($localuser instanceof User) { - $localuser->delete(); - } - - // Warning: delete() will run on the batch objects, - // not on individual objects. - $related = [ - 'Reply', - 'Group_member', - 'Profile_role', - ]; - Event::handle('ProfileDeleteRelated', array($this, &$related)); - - foreach ($related as $cls) { - $inst = new $cls(); - $inst->profile_id = $this->id; - $inst->delete(); - } - - return parent::delete($useWhere); - } - - public function _deleteNotices() - { - $notice = new Notice(); - $notice->profile_id = $this->id; - - if ($notice->find()) { - while ($notice->fetch()) { - $other = clone($notice); - $other->delete(); - } - } - } - - public function _deleteSubscriptions() - { - $sub = new Subscription(); - $sub->subscriber = $this->getID(); - $sub->find(); - - while ($sub->fetch()) { - try { - $other = $sub->getSubscribed(); - if (!$other->sameAs($this)) { - Subscription::cancel($this, $other); - } - } catch (NoResultException $e) { - // Profile not found - common_log(LOG_INFO, 'Subscribed profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...'); - } catch (ServerException $e) { - // Subscription cancel failed - common_log(LOG_INFO, 'Subscribed profile id=='.$other->getID().' could not be reached for unsubscription notice when deleting profile id=='.$this->getID().', ignoring...'); - } - } - - $sub = new Subscription(); - $sub->subscribed = $this->getID(); - $sub->find(); - - while ($sub->fetch()) { - try { - $other = $sub->getSubscriber(); - common_log(LOG_INFO, 'Subscriber profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...'); - if (!$other->sameAs($this)) { - Subscription::cancel($other, $this); - } - } catch (NoResultException $e) { - // Profile not found - common_log(LOG_INFO, 'Subscribed profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...'); - } catch (ServerException $e) { - // Subscription cancel failed - common_log(LOG_INFO, 'Subscriber profile id=='.$other->getID().' could not be reached for unsubscription notice when deleting profile id=='.$this->getID().', ignoring...'); - } - } - - // Finally delete self-subscription - $self = new Subscription(); - $self->subscriber = $this->getID(); - $self->subscribed = $this->getID(); - $self->delete(); - } - - public function _deleteTags() - { - $tag = new Profile_tag(); - $tag->tagged = $this->id; - $tag->delete(); - } - - public function _deleteBlocks() - { - $block = new Profile_block(); - $block->blocked = $this->id; - $block->delete(); - - $block = new Group_block(); - $block->blocked = $this->id; - $block->delete(); - } - - public function _deleteAttentions() - { - $att = new Attention(); - $att->profile_id = $this->getID(); - - if ($att->find()) { - while ($att->fetch()) { - // Can't do delete() on the object directly since it won't remove all of it - $other = clone($att); - $other->delete(); - } - } - } - - // XXX: identical to Notice::getLocation. - - public function getLocation() - { - $location = null; - - if (!empty($this->location_id) && !empty($this->location_ns)) { - $location = Location::fromId($this->location_id, $this->location_ns); - } - - if (is_null($location)) { // no ID, or Location::fromId() failed - if (!empty($this->lat) && !empty($this->lon)) { - $location = Location::fromLatLon($this->lat, $this->lon); - } - } - - if (is_null($location)) { // still haven't found it! - if (!empty($this->location)) { - $location = Location::fromName($this->location); - } - } - - return $location; - } - - public function shareLocation() - { - $cfg = common_config('location', 'share'); - - if ($cfg == 'always') { - return true; - } elseif ($cfg == 'never') { - return false; - } else { // user - $share = common_config('location', 'sharedefault'); - - // Check if user has a personal setting for this - $prefs = User_location_prefs::getKV('user_id', $this->id); - - if (!empty($prefs)) { - $share = $prefs->share_location; - $prefs->free(); - } - - return $share; - } - } - - public function hasRole($name) - { - $has_role = false; - if (Event::handle('StartHasRole', array($this, $name, &$has_role))) { - $role = Profile_role::pkeyGet(array('profile_id' => $this->id, - 'role' => $name)); - $has_role = !empty($role); - Event::handle('EndHasRole', array($this, $name, $has_role)); - } - return $has_role; - } - - public function grantRole($name) - { - if (Event::handle('StartGrantRole', array($this, $name))) { - $role = new Profile_role(); - - $role->profile_id = $this->id; - $role->role = $name; - $role->created = common_sql_now(); - - $result = $role->update(); - if ($result === 0 || $result === false) { - $result = $role->insert(); - } - - if (!$result) { - throw new Exception("Can't save role '$name' for profile '{$this->id}'"); - } - - if ($name == 'owner') { - User::blow('user:site_owner'); - } - - Event::handle('EndGrantRole', array($this, $name)); - } - - return $result; - } - - public function revokeRole($name) - { - if (Event::handle('StartRevokeRole', array($this, $name))) { - $role = Profile_role::pkeyGet(array('profile_id' => $this->id, - 'role' => $name)); - - if (empty($role)) { - // TRANS: Exception thrown when trying to revoke an existing role for a user that does not exist. - // TRANS: %1$s is the role name, %2$s is the user ID (number). - throw new Exception(sprintf( - _('Cannot revoke role "%1$s" for user #%2$d; does not exist.'), - $name, - $this->id - )); - } - - $result = $role->delete(); - - if (!$result) { - common_log_db_error($role, 'DELETE', __FILE__); - // TRANS: Exception thrown when trying to revoke a role for a user with a failing database query. - // TRANS: %1$s is the role name, %2$s is the user ID (number). - throw new Exception(sprintf( - _('Cannot revoke role "%1$s" for user #%2$d; database error.'), - $name, - $this->id - )); - } - - if ($name == 'owner') { - User::blow('user:site_owner'); - } - - Event::handle('EndRevokeRole', array($this, $name)); - - return true; - } - } - - public function isSandboxed() - { - return $this->hasRole(Profile_role::SANDBOXED); - } - - public function isSilenced() - { - return $this->hasRole(Profile_role::SILENCED); - } - - public function sandbox() - { - $this->grantRole(Profile_role::SANDBOXED); - } - - public function unsandbox() - { - $this->revokeRole(Profile_role::SANDBOXED); - } - - public function silence() - { - $this->grantRole(Profile_role::SILENCED); - if (common_config('notice', 'hidespam')) { - $this->flushVisibility(); - } - } - - public function silenceAs(Profile $actor) - { - if (!$actor->hasRight(Right::SILENCEUSER)) { - throw new AuthorizationException(_('You cannot silence users on this site.')); - } - // Only administrators can silence other privileged users (such as others who have the right to silence). - if ($this->isPrivileged() && !$actor->hasRole(Profile_role::ADMINISTRATOR)) { - throw new AuthorizationException(_('You cannot silence other privileged users.')); - } - if ($this->isSilenced()) { - // TRANS: Client error displayed trying to silence an already silenced user. - throw new AlreadyFulfilledException(_('User is already silenced.')); - } - return $this->silence(); - } - - public function unsilence() - { - $this->revokeRole(Profile_role::SILENCED); - if (common_config('notice', 'hidespam')) { - $this->flushVisibility(); - } - } - - public function unsilenceAs(Profile $actor) - { - if (!$actor->hasRight(Right::SILENCEUSER)) { - // TRANS: Client error displayed trying to unsilence a user when the user does not have the right. - throw new AuthorizationException(_('You cannot unsilence users on this site.')); - } - if (!$this->isSilenced()) { - // TRANS: Client error displayed trying to unsilence a user when the target user has not been silenced. - throw new AlreadyFulfilledException(_('User is not silenced.')); - } - return $this->unsilence(); - } - - public function flushVisibility() - { - // Get all notices - $stream = new ProfileNoticeStream($this, $this); - $ids = $stream->getNoticeIds(0, CachingNoticeStream::CACHE_WINDOW); - foreach ($ids as $id) { - self::blow('notice:in-scope-for:%d:null', $id); - } - } - - public function isPrivileged() - { - // TODO: An Event::handle so plugins can report if users are privileged. - // The ModHelper is the only one I care about when coding this, and that - // can be tested with Right::SILENCEUSER which I do below: - switch (true) { - case $this->hasRight(Right::SILENCEUSER): - case $this->hasRole(Profile_role::MODERATOR): - case $this->hasRole(Profile_role::ADMINISTRATOR): - case $this->hasRole(Profile_role::OWNER): - return true; - } - - return false; - } - - /** - * Does this user have the right to do X? - * - * With our role-based authorization, this is merely a lookup for whether the user - * has a particular role. The implementation currently uses a switch statement - * to determine if the user has the pre-defined role to exercise the right. Future - * implementations may allow per-site roles, and different mappings of roles to rights. - * - * @param $right string Name of the right, usually a constant in class Right - * @return boolean whether the user has the right in question - */ - public function hasRight($right) - { - $result = false; - - if ($this->hasRole(Profile_role::DELETED)) { - return false; - } - - if (Event::handle('UserRightsCheck', array($this, $right, &$result))) { - switch ($right) { - case Right::DELETEOTHERSNOTICE: - case Right::MAKEGROUPADMIN: - case Right::SANDBOXUSER: - case Right::SILENCEUSER: - case Right::DELETEUSER: - case Right::DELETEGROUP: - case Right::TRAINSPAM: - case Right::REVIEWSPAM: - $result = $this->hasRole(Profile_role::MODERATOR); - break; - case Right::CONFIGURESITE: - $result = $this->hasRole(Profile_role::ADMINISTRATOR); - break; - case Right::GRANTROLE: - case Right::REVOKEROLE: - $result = $this->hasRole(Profile_role::OWNER); - break; - case Right::NEWNOTICE: - case Right::NEWMESSAGE: - case Right::SUBSCRIBE: - case Right::CREATEGROUP: - $result = !$this->isSilenced(); - break; - case Right::PUBLICNOTICE: - case Right::EMAILONREPLY: - case Right::EMAILONSUBSCRIBE: - case Right::EMAILONFAVE: - $result = !$this->isSandboxed() && !$this->isSilenced(); - break; - case Right::WEBLOGIN: - $result = !$this->isSilenced(); - break; - case Right::API: - $result = !$this->isSilenced(); - break; - case Right::BACKUPACCOUNT: - $result = common_config('profile', 'backup'); - break; - case Right::RESTOREACCOUNT: - $result = common_config('profile', 'restore'); - break; - case Right::DELETEACCOUNT: - $result = common_config('profile', 'delete'); - break; - case Right::MOVEACCOUNT: - $result = common_config('profile', 'move'); - break; - default: - $result = false; - break; - } - } - return $result; - } - - // FIXME: Can't put Notice typing here due to ArrayWrapper - public function hasRepeated($notice) - { - // XXX: not really a pkey, but should work - - $notice = Notice::pkeyGet(array('profile_id' => $this->getID(), - 'repeat_of' => $notice->getID(), - 'verb' => ActivityVerb::SHARE)); - - return !empty($notice); - } - - /** - * Returns an XML string fragment with limited profile information - * as an Atom element. - * - * Assumes that Atom has been previously set up as the base namespace. - * - * @param Profile $cur the current authenticated user - * - * @return string - */ - public function asAtomAuthor($cur = null) - { - $xs = new XMLStringer(true); - - $xs->elementStart('author'); - $xs->element('name', null, $this->nickname); - $xs->element('uri', null, $this->getUri()); - if ($cur != null) { - $attrs = []; - $attrs['following'] = $cur->isSubscribed($this) ? 'true' : 'false'; - $attrs['blocking'] = $cur->hasBlocked($this) ? 'true' : 'false'; - $xs->element('statusnet:profile_info', $attrs, null); - } - $xs->elementEnd('author'); - - return $xs->getString(); - } - - /** - * Extra profile info for atom entries - * - * Clients use some extra profile info in the atom stream. - * This gives it to them. - * - * @param Profile $scoped The currently logged in/scoped profile - * - * @return array representation of element or null - */ - - public function profileInfo(Profile $scoped = null) - { - $profileInfoAttr = array('local_id' => $this->id); - - if ($scoped instanceof Profile) { - // Whether the current user is a subscribed to this profile - $profileInfoAttr['following'] = $scoped->isSubscribed($this) ? 'true' : 'false'; - // Whether the current user is has blocked this profile - $profileInfoAttr['blocking'] = $scoped->hasBlocked($this) ? 'true' : 'false'; - } - - return array('statusnet:profile_info', $profileInfoAttr, null); - } - - /** - * Returns an XML string fragment with profile information as an - * Activity Streams element. - * - * Assumes that 'activity' namespace has been previously defined. - * - * @return string - */ - public function asActivityActor() - { - return $this->asActivityNoun('actor'); - } - - /** - * Returns an XML string fragment with profile information as an - * Activity Streams noun object with the given element type. - * - * Assumes that 'activity', 'georss', and 'poco' namespace has been - * previously defined. - * - * @param string $element one of 'actor', 'subject', 'object', 'target' - * - * @return string - */ - public function asActivityNoun($element) - { - $noun = $this->asActivityObject(); - return $noun->asString('activity:' . $element); - } - - public function asActivityObject() - { - $object = new ActivityObject(); - - if (Event::handle('StartActivityObjectFromProfile', array($this, &$object))) { - $object->type = $this->getObjectType(); - $object->id = $this->getUri(); - $object->title = $this->getBestName(); - $object->link = $this->getUrl(); - $object->summary = $this->getDescription(); - - try { - $avatar = Avatar::getUploaded($this); - $object->avatarLinks[] = AvatarLink::fromAvatar($avatar); - } catch (NoAvatarException $e) { - // Could not find an original avatar to link - } - - $sizes = array( - AVATAR_PROFILE_SIZE, - AVATAR_STREAM_SIZE, - AVATAR_MINI_SIZE - ); - - foreach ($sizes as $size) { - $alink = null; - try { - $avatar = Avatar::byProfile($this, $size); - $alink = AvatarLink::fromAvatar($avatar); - } catch (NoAvatarException $e) { - $alink = new AvatarLink(); - $alink->type = 'image/png'; - $alink->height = $size; - $alink->width = $size; - $alink->url = Avatar::defaultImage($size); - } - - $object->avatarLinks[] = $alink; - } - - if (isset($this->lat) && isset($this->lon)) { - $object->geopoint = (float)$this->lat - . ' ' . (float)$this->lon; - } - - $object->poco = PoCo::fromProfile($this); - - if ($this->isLocal()) { - $object->extra[] = array('followers', array('url' => common_local_url('subscribers', array('nickname' => $this->getNickname())))); - } - - Event::handle('EndActivityObjectFromProfile', array($this, &$object)); - } - - return $object; - } - - /** - * Returns the profile's canonical url, not necessarily a uri/unique id - * - * @return string $profileurl - */ - public function getUrl() - { - $url = null; - if ($this->isGroup()) { - // FIXME: Get rid of this event, it fills no real purpose, data should be in Profile->profileurl (replaces User_group->mainpage) - if (Event::handle('StartUserGroupHomeUrl', array($this->getGroup(), &$url))) { - $url = $this->getGroup()->isLocal() - ? common_local_url('showgroup', array('nickname' => $this->getNickname())) - : $this->profileurl; - } - Event::handle('EndUserGroupHomeUrl', array($this->getGroup(), $url)); - } elseif ($this->isLocal()) { - $url = common_local_url('showstream', array('nickname' => $this->getNickname())); - } else { - $url = $this->profileurl; - } - if (empty($url) || - !filter_var($url, FILTER_VALIDATE_URL)) { - throw new InvalidUrlException($url); - } - return $url; - } - public function getHtmlTitle() - { - try { - return $this->getAcctUri(false); - } catch (ProfileNoAcctUriException $e) { - return $this->getNickname(); - } - } - - public function getNickname() - { - return $this->nickname; - } - - public function getFullname() - { - return $this->fullname; - } - - public function getHomepage() - { - return $this->homepage; - } - - public function getDescription() - { - return $this->bio; - } - - /** - * Returns the best URI for a profile. Plugins may override. - * - * @return string $uri - */ - public function getUri() - { - $uri = null; - - // give plugins a chance to set the URI - if (Event::handle('StartGetProfileUri', array($this, &$uri))) { - - // check for a local user first - $user = User::getKV('id', $this->id); - if ($user instanceof User) { - $uri = $user->getUri(); - } else { - $group = User_group::getKV('profile_id', $this->id); - if ($group instanceof User_group) { - $uri = $group->getUri(); - } - } - - Event::handle('EndGetProfileUri', array($this, &$uri)); - } - - return $uri; - } - - /** - * Returns an assumed acct: URI for a profile. Plugins are required. - * - * @return string $uri - */ - public function getAcctUri($scheme=true) - { - $acct = null; - - if (Event::handle('StartGetProfileAcctUri', array($this, &$acct))) { - Event::handle('EndGetProfileAcctUri', array($this, &$acct)); - } - - if ($acct === null) { - throw new ProfileNoAcctUriException($this); - } - if (parse_url($acct, PHP_URL_SCHEME) !== 'acct') { - throw new ServerException('Acct URI does not have acct: scheme'); - } - - // if we don't return the scheme, just remove the 'acct:' in the beginning - return $scheme ? $acct : mb_substr($acct, 5); - } - - public function hasBlocked(Profile $other) - { - $block = Profile_block::exists($this, $other); - return !empty($block); - } - - public function getAtomFeed() - { - $feed = null; - - if (Event::handle('StartProfileGetAtomFeed', array($this, &$feed))) { - if ($this->isLocal()) { - $feed = common_local_url('ApiTimelineUser', array('id' => $this->getID(), - 'format' => 'atom')); - } - Event::handle('EndProfileGetAtomFeed', array($this, $feed)); - } - - return $feed; - } - - public function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) - { - // TRANS: Exception thrown when trying view "repeated to me". - throw new Exception(_('Not implemented since inbox change.')); - } - - /* - * Get a Profile object by URI. Will call external plugins for help - * using the event StartGetProfileFromURI. - * - * @param string $uri A unique identifier for a resource (profile/group/whatever) - */ - public static function fromUri($uri) - { - $profile = null; - - if (Event::handle('StartGetProfileFromURI', array($uri, &$profile))) { - // Get a local user when plugin lookup (like OStatus) fails - $user = User::getKV('uri', $uri); - if ($user instanceof User) { - $profile = $user->getProfile(); - } else { - $group = User_group::getKV('uri', $uri); - if ($group instanceof User_group) { - $profile = $group->getProfile(); - } - } - Event::handle('EndGetProfileFromURI', array($uri, $profile)); - } - - if (!$profile instanceof Profile) { - throw new UnknownUriException($uri); - } - - return $profile; - } - - public function canRead(Notice $notice) - { - if ($notice->scope & Notice::SITE_SCOPE) { - $user = $this->getUser(); - if (empty($user)) { - return false; - } - } - - if ($notice->scope & Notice::ADDRESSEE_SCOPE) { - $replies = $notice->getReplies(); - - if (!in_array($this->id, $replies)) { - $groups = $notice->getGroups(); - - $foundOne = false; - - foreach ($groups as $group) { - if ($this->isMember($group)) { - $foundOne = true; - break; - } - } - - if (!$foundOne) { - return false; - } - } - } - - if ($notice->scope & Notice::FOLLOWER_SCOPE) { - $author = $notice->getProfile(); - if (!Subscription::exists($this, $author)) { - return false; - } - } - - return true; - } - - public static function current() - { - $user = common_current_user(); - if (empty($user)) { - $profile = null; - } else { - $profile = $user->getProfile(); - } - return $profile; - } - - public static function ensureCurrent() - { - $profile = self::current(); - if (!$profile instanceof Profile) { - throw new AuthorizationException('A currently scoped profile is required.'); - } - return $profile; - } - - /** - * Magic function called at serialize() time. - * - * We use this to drop a couple process-specific references - * from DB_DataObject which can cause trouble in future - * processes. - * - * @return array of variable names to include in serialization. - */ - - public function __sleep() - { - $vars = parent::__sleep(); - $skip = array('_user', '_group'); - return array_diff($vars, $skip); - } - - public function getProfile() - { - return $this; - } - - /** - * Test whether the given profile is the same as the current class, - * for testing identities. - * - * @param Profile $other The other profile, usually from Action's $this->scoped - * - * @return boolean - */ - public function sameAs(Profile $other=null) - { - if (is_null($other)) { - // In case $this->scoped is null or something, i.e. not a current/legitimate profile. - return false; - } - return $this->getID() === $other->getID(); - } - - /** - * This will perform shortenLinks with the connected User object. - * - * Won't work on remote profiles or groups, so expect a - * NoSuchUserException if you don't know it's a local User. - * - * @param string $text String to shorten - * @param boolean $always Disrespect minimum length etc. - * - * @return string link-shortened $text - */ - public function shortenLinks($text, $always=false) - { - return $this->getUser()->shortenLinks($text, $always); - } - - public function isPrivateStream(): bool - { - // We only know of public remote users as of yet... - if (!$this->isLocal()) { - return false; - } - $private_stream = $this->getUser()->private_stream; - return !is_null($private_stream) && $private_stream; - } - - public function delPref($namespace, $topic) - { - return Profile_prefs::setData($this, $namespace, $topic, null); - } - - public function getPref($namespace, $topic, $default = null) - { - // If you want an exception to be thrown, call Profile_prefs::getData directly - try { - return Profile_prefs::getData($this, $namespace, $topic, $default); - } catch (NoResultException $e) { - return null; - } - } - - // The same as getPref but will fall back to common_config value for the same namespace/topic - public function getConfigPref($namespace, $topic) - { - return Profile_prefs::getConfigData($this, $namespace, $topic); - } - - public function setPref($namespace, $topic, $data) - { - return Profile_prefs::setData($this, $namespace, $topic, $data); - } - - public function getConnectedApps($offset=0, $limit=null) - { - return $this->getUser()->getConnectedApps($offset, $limit); - } -} diff --git a/src/Entity/ProfileBlock.php b/src/Entity/ProfileBlock.php new file mode 100644 index 0000000000..56bfd1ffd5 --- /dev/null +++ b/src/Entity/ProfileBlock.php @@ -0,0 +1,58 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for User's Profile Block + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ProfileBlock +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'profile_block', + 'fields' => [ + 'blocker' => ['type' => 'int', 'not null' => true, 'description' => 'user making the block'], + 'blocked' => ['type' => 'int', 'not null' => true, 'description' => 'profile that is blocked'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date of blocking'], + ], + 'foreign keys' => [ + 'profile_block_blocker_fkey' => ['user', ['blocker' => 'id']], + 'profile_block_blocked_fkey' => ['profile', ['blocked' => 'id']], + ], + 'primary key' => ['blocker', 'blocked'], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ProfileList.php b/src/Entity/ProfileList.php new file mode 100644 index 0000000000..ad5e551e31 --- /dev/null +++ b/src/Entity/ProfileList.php @@ -0,0 +1,77 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for List of profiles + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ProfileList +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'profile_list', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], + 'tagger' => ['type' => 'int', 'not null' => true, 'description' => 'user making the tag'], + 'tag' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'people tag'], + 'description' => ['type' => 'text', 'description' => 'description of the people tag'], + 'private' => ['type' => 'bool', 'default' => false, 'description' => 'is this tag private'], + + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date the tag was added'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date the tag was modified'], + + 'uri' => ['type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'], + 'mainpage' => ['type' => 'varchar', 'length' => 191, 'description' => 'page to link to'], + 'tagged_count' => ['type' => 'int', 'default' => 0, 'description' => 'number of people tagged with this tag by this user'], + 'subscriber_count' => ['type' => 'int', 'default' => 0, 'description' => 'number of subscribers to this tag'], + ], + 'primary key' => ['tagger', 'tag'], + 'unique keys' => [ + 'profile_list_id_key' => ['id'], + ], + 'foreign keys' => [ + 'profile_list_tagger_fkey' => ['profile', ['tagger' => 'id']], + ], + 'indexes' => [ + 'profile_list_modified_idx' => ['modified'], + 'profile_list_tag_idx' => ['tag'], + 'profile_list_tagger_tag_idx' => ['tagger', 'tag'], + 'profile_list_tagged_count_idx' => ['tagged_count'], + 'profile_list_subscriber_count_idx' => ['subscriber_count'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ProfilePrefs.php b/src/Entity/ProfilePrefs.php new file mode 100644 index 0000000000..0aa7ffc09f --- /dev/null +++ b/src/Entity/ProfilePrefs.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Data class for Profile preferences + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ProfilePrefs +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'profile_prefs', + 'fields' => [ + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'user'], + 'namespace' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'], + 'topic' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'], + 'data' => ['type' => 'blob', 'description' => 'topic data, may be anything'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['profile_id', 'namespace', 'topic'], + 'foreign keys' => [ + 'profile_prefs_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + 'profile_prefs_profile_id_idx' => ['profile_id'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ProfileRole.php b/src/Entity/ProfileRole.php new file mode 100644 index 0000000000..bf7f570227 --- /dev/null +++ b/src/Entity/ProfileRole.php @@ -0,0 +1,58 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user profile role + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ProfileRole +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'profile_role', + 'fields' => [ + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'account having the role'], + 'role' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'string representing the role'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date the role was granted'], + ], + 'primary key' => ['profile_id', 'role'], + 'foreign keys' => [ + 'profile_role_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => ['profile_role_role_created_profile_id_idx' => ['role', 'created', 'profile_id']], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ProfileTag.php b/src/Entity/ProfileTag.php new file mode 100644 index 0000000000..00ac7b9105 --- /dev/null +++ b/src/Entity/ProfileTag.php @@ -0,0 +1,65 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Profile Tag + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ProfileTag +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'profile_tag', + + 'fields' => [ + 'tagger' => ['type' => 'int', 'not null' => true, 'description' => 'user making the tag'], + 'tagged' => ['type' => 'int', 'not null' => true, 'description' => 'profile tagged'], + 'tag' => ['type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this notice'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date the tag was added'], + ], + 'primary key' => ['tagger', 'tagged', 'tag'], + 'foreign keys' => [ + 'profile_tag_tagger_fkey' => ['profile', ['tagger' => 'id']], + 'profile_tag_tagged_fkey' => ['profile', ['tagged' => 'id']], + ], + 'indexes' => [ + 'profile_tag_modified_idx' => ['modified'], + 'profile_tag_tagger_tag_idx' => ['tagger', 'tag'], + 'profile_tag_tagged_idx' => ['tagged'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/ProfileTagSubscription.php b/src/Entity/ProfileTagSubscription.php new file mode 100644 index 0000000000..b7f5ab8278 --- /dev/null +++ b/src/Entity/ProfileTagSubscription.php @@ -0,0 +1,65 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Profile Tag Subscription + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class ProfileTagSubscription +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'profile_tag_subscription', + 'fields' => [ + 'profile_tag_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to profile_tag'], + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'], + + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['profile_tag_id', 'profile_id'], + 'foreign keys' => [ + 'profile_tag_subscription_profile_list_id_fkey' => ['profile_list', ['profile_tag_id' => 'id']], + 'profile_tag_subscription_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + // @fixme probably we want a (profile_id, created) index here? + 'profile_tag_subscription_profile_id_idx' => ['profile_id'], + 'profile_tag_subscription_created_idx' => ['created'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Profile_block.php b/src/Entity/Profile_block.php deleted file mode 100644 index d4e7c83cf2..0000000000 --- a/src/Entity/Profile_block.php +++ /dev/null @@ -1,63 +0,0 @@ -. - -/* - * Table Definition for profile_block - * - * @copyright 2008, 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Profile_block extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'profile_block'; // table name - public $blocker; // int(4) primary_key not_null - public $blocked; // int(4) primary_key not_null - public $modified; // datetime() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'blocker' => array('type' => 'int', 'not null' => true, 'description' => 'user making the block'), - 'blocked' => array('type' => 'int', 'not null' => true, 'description' => 'profile that is blocked'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date of blocking'), - ), - 'foreign keys' => array( - 'profile_block_blocker_fkey' => array('user', array('blocker' => 'id')), - 'profile_block_blocked_fkey' => array('profile', array('blocked' => 'id')), - ), - 'primary key' => array('blocker', 'blocked'), - 'indexes' => array( - 'profile_block_blocked_idx' => array('blocked'), - ), - ); - } - - public static function exists(Profile $blocker, Profile $blocked) - { - return Profile_block::pkeyGet(array('blocker' => $blocker->id, - 'blocked' => $blocked->id)); - } -} diff --git a/src/Entity/Profile_list.php b/src/Entity/Profile_list.php deleted file mode 100644 index e688969e20..0000000000 --- a/src/Entity/Profile_list.php +++ /dev/null @@ -1,949 +0,0 @@ -. - -/** - * @category Notices - * @package GNUsocial - * @author Shashi Gowda - * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Profile_list extends Managed_DataObject -{ - public $__table = 'profile_list'; // table name - public $id; // int(4) primary_key not_null - public $tagger; // int(4) - public $tag; // varchar(64) - public $description; // text - public $private; // bool default_false - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $mainpage; // varchar(191) not 255 because utf8mb4 takes more space - public $tagged_count; // smallint - public $subscriber_count; // smallint - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), - 'tagger' => array('type' => 'int', 'not null' => true, 'description' => 'user making the tag'), - 'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'people tag'), - 'description' => array('type' => 'text', 'description' => 'description of the people tag'), - 'private' => array('type' => 'bool', 'default' => false, 'description' => 'is this tag private'), - - 'created' => array('type' => 'datetime', 'description' => 'date the tag was added'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date the tag was modified'), - - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'), - 'mainpage' => array('type' => 'varchar', 'length' => 191, 'description' => 'page to link to'), - 'tagged_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of people tagged with this tag by this user'), - 'subscriber_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of subscribers to this tag'), - ), - 'primary key' => array('tagger', 'tag'), - 'unique keys' => array( - 'profile_list_id_key' => array('id'), - ), - 'foreign keys' => array( - 'profile_list_tagger_fkey' => array('profile', array('tagger' => 'id')), - ), - 'indexes' => array( - 'profile_list_modified_id_idx' => array('modified', 'id'), - 'profile_list_tag_idx' => array('tag'), - 'profile_list_tagged_count_idx' => array('tagged_count'), - 'profile_list_subscriber_count_idx' => array('subscriber_count'), - ), - ); - } - - /** - * get the tagger of this profile_list object - * - * @return Profile the tagger - */ - - public function getTagger() - { - return Profile::getByID($this->tagger); - } - - /** - * return a string to identify this - * profile_list in the user interface etc. - * - * @return String - */ - - public function getBestName() - { - return $this->tag; - } - - /** - * return a uri string for this profile_list - * - * @return String uri - */ - - public function getUri() - { - $uri = null; - if (Event::handle('StartProfiletagGetUri', array($this, &$uri))) { - if (!empty($this->uri)) { - $uri = $this->uri; - } else { - $uri = common_local_url( - 'profiletagbyid', - ['id' => $this->id, 'tagger_id' => $this->tagger] - ); - } - } - Event::handle('EndProfiletagGetUri', array($this, &$uri)); - return $uri; - } - - /** - * return a url to the homepage of this item - * - * @return String home url - */ - - public function homeUrl() - { - $url = null; - if (Event::handle('StartUserPeopletagHomeUrl', array($this, &$url))) { - // normally stored in mainpage, but older ones may be null - if (!empty($this->mainpage)) { - $url = $this->mainpage; - } else { - $url = common_local_url( - 'showprofiletag', - [ - 'nickname' => $this->getTagger()->nickname, - 'tag' => $this->tag, - ] - ); - } - } - Event::handle('EndUserPeopletagHomeUrl', array($this, &$url)); - return $url; - } - - /** - * return an immutable url for this object - * - * @return String permalink - */ - - public function permalink() - { - $url = null; - if (Event::handle('StartProfiletagPermalink', array($this, &$url))) { - $url = common_local_url( - 'profiletagbyid', - ['id' => $this->id] - ); - } - Event::handle('EndProfiletagPermalink', array($this, &$url)); - return $url; - } - - /** - * Query notices by users associated with this tag, - * but first check the cache before hitting the DB. - * - * @param integer $offset offset - * @param integer $limit maximum no of results - * @param integer $since_id=null since this id - * @param integer $max_id=null maximum id in result - * - * @return Notice the query - */ - - public function getNotices($offset, $limit, $since_id = null, $max_id = null) - { - // FIXME: Use something else than Profile::current() to avoid - // possible confusion between session user and queue processing. - $stream = new PeopletagNoticeStream($this, Profile::current()); - - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - /** - * Get subscribers (local and remote) to this people tag - * Order by reverse chronology - * - * @param integer $offset offset - * @param integer $limit maximum no of results - * @param integer $since_id=null since unix timestamp - * @param integer $upto=null maximum unix timestamp when subscription was made - * - * @return Profile results - */ - - public function getSubscribers(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) - { - $subs = new Profile(); - - $subs->joinAdd( - array('id', 'profile_tag_subscription:profile_id') - ); - $subs->whereAdd('profile_tag_subscription.profile_tag_id = ' . $this->id); - - if (common_config('db', 'type') !== 'mysql') { - $subs->selectAdd(sprintf( - '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . - 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', - "FROM (profile_tag_subscription.created - TIMESTAMP '1970-01-01 00:00:00')" - )); - } else { - $subs->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag_subscription.created) AS `cursor`"); - } - - if ($since != 0) { - $subs->whereAdd('cursor > ' . $since); - } - - if ($upto != 0) { - $subs->whereAdd('cursor <= ' . $upto); - } - - if (!is_null($limit)) { - $subs->limit($offset, $limit); - } - - $subs->orderBy('profile_tag_subscription.created DESC, profile.id DESC'); - $subs->find(); - - return $subs; - } - - /** - * Get all and only local subscribers to this people tag - * used for distributing notices to user inboxes. - * - * @return array ids of users - */ - - public function getUserSubscribers() - { - // XXX: cache this - - $user = new User(); - - $user->query(sprintf( - 'SELECT id ' . - 'FROM %1$s INNER JOIN profile_tag_subscription ' . - 'ON %1$s.id = profile_tag_subscription.profile_id ' . - 'WHERE profile_tag_subscription.profile_tag_id = %2$d ', - $user->escapedTableName(), - $this->id - )); - - $ids = []; - - while ($user->fetch()) { - $ids[] = $user->id; - } - - $user->free(); - - return $ids; - } - - /** - * Check to see if a given profile has - * subscribed to this people tag's timeline - * - * @param mixed $id User or Profile object or integer id - * - * @return boolean subscription status - */ - - public function hasSubscriber($id) - { - if (!is_numeric($id)) { - $id = $id->id; - } - - $sub = Profile_tag_subscription::pkeyGet(array('profile_tag_id' => $this->id, - 'profile_id' => $id)); - return !empty($sub); - } - - /** - * Get profiles tagged with this people tag, - * include modified timestamp as a "cursor" field - * order by descending order of modified time - * - * @param integer $offset offset - * @param integer $limit maximum no of results - * @param integer $since_id=null since unix timestamp - * @param integer $upto=null maximum unix timestamp when subscription was made - * - * @return Profile results - */ - - public function getTagged(int $offset = 0, ?int $limit = null, int $since = 0, int $upto = 0) - { - $tagged = new Profile(); - $tagged->joinAdd(['id', 'profile_tag:tagged']); - - if (common_config('db', 'type') !== 'mysql') { - $tagged->selectAdd(sprintf( - '((EXTRACT(DAY %1$s) * 24 + EXTRACT(HOUR %1$s)) * 60 + ' . - 'EXTRACT(MINUTE %1$s)) * 60 + FLOOR(EXTRACT(SECOND %1$s)) AS "cursor"', - "FROM (profile_tag.modified - TIMESTAMP '1970-01-01 00:00:00')" - )); - } else { - $tagged->selectAdd("timestampdiff(SECOND, '1970-01-01', profile_tag.modified) AS `cursor`"); - } - - $tagged->whereAdd('profile_tag.tagger = '.$this->tagger); - $tagged->whereAdd("profile_tag.tag = '{$this->tag}'"); - - if ($since != 0) { - $tagged->whereAdd('cursor > ' . $since); - } - - if ($upto != 0) { - $tagged->whereAdd('cursor <= ' . $upto); - } - - if (!is_null($limit)) { - $tagged->limit($offset, $limit); - } - - $tagged->orderBy('profile_tag.modified DESC, profile_tag.tagged DESC'); - $tagged->find(); - - return $tagged; - } - - /** - * Gracefully delete one or many people tags - * along with their members and subscriptions data - * - * @return boolean success - */ - - public function delete($useWhere = false) - { - // force delete one item at a time. - if (empty($this->id)) { - $this->find(); - while ($this->fetch()) { - $this->delete(); - } - } - - Profile_tag::cleanup($this); - Profile_tag_subscription::cleanup($this); - - self::blow('profile:lists:%d', $this->tagger); - - return parent::delete($useWhere); - } - - /** - * Update a people tag gracefully - * also change "tag" fields in profile_tag table - * - * @param Profile_list $dataObject Object's original form - * - * @return boolean success - */ - - public function update($dataObject = false) - { - if (!is_object($dataObject) && !$dataObject instanceof Profile_list) { - return parent::update($dataObject); - } - - $result = true; - - // if original tag was different - // check to see if the new tag already exists - // if not, rename the tag correctly - if ($dataObject->tag != $this->tag || $dataObject->tagger != $this->tagger) { - $existing = Profile_list::getByTaggerAndTag($this->tagger, $this->tag); - if (!empty($existing)) { - // TRANS: Server exception. - throw new ServerException(_('The tag you are trying to rename ' . - 'to already exists.')); - } - // move the tag - // XXX: allow OStatus plugin to send out profile tag - $result = Profile_tag::moveTag($dataObject, $this); - } - return parent::update($dataObject); - } - - /** - * return an xml string representing this people tag - * as the author of an atom feed - * - * @return string atom author element - */ - - public function asAtomAuthor() - { - $xs = new XMLStringer(true); - - $tagger = $this->getTagger(); - $xs->elementStart('author'); - $xs->element('name', null, '@' . $tagger->nickname . '/' . $this->tag); - $xs->element('uri', null, $this->permalink()); - $xs->elementEnd('author'); - - return $xs->getString(); - } - - /** - * return an xml string to represent this people tag - * as a noun in an activitystreams feed. - * - * @param string $element the xml tag - * - * @return string activitystreams noun - */ - - public function asActivityNoun($element) - { - $noun = ActivityObject::fromPeopletag($this); - return $noun->asString('activity:' . $element); - } - - /** - * get the cached number of profiles tagged with this - * people tag, re-count if the argument is true. - * - * @param boolean $recount whether to ignore cache - * - * @return integer count - */ - - public function taggedCount($recount = false) - { - $keypart = sprintf( - 'profile_list:tagged_count:%d:%s', - $this->tagger, - $this->tag - ); - - $count = self::cacheGet($keypart); - - if ($count === false) { - $tags = new Profile_tag(); - - $tags->tag = $this->tag; - $tags->tagger = $this->tagger; - - $count = $tags->count('distinct tagged'); - - self::cacheSet($keypart, $count); - } - - return $count; - } - - /** - * get the cached number of profiles subscribed to this - * people tag, re-count if the argument is true. - * - * @param boolean $recount whether to ignore cache - * - * @return integer count - */ - - public function subscriberCount($recount = false) - { - $keypart = sprintf( - 'profile_list:subscriber_count:%d', - $this->id - ); - - $count = self::cacheGet($keypart); - - if ($count === false) { - $sub = new Profile_tag_subscription(); - $sub->profile_tag_id = $this->id; - $count = (int) $sub->count('distinct profile_id'); - - self::cacheSet($keypart, $count); - } - - return $count; - } - - /** - * get the cached number of profiles subscribed to this - * people tag, re-count if the argument is true. - * - * @param boolean $recount whether to ignore cache - * - * @return integer count - */ - - public function blowNoticeStreamCache($all = false) - { - self::blow('profile_list:notice_ids:%d', $this->id); - if ($all) { - self::blow('profile_list:notice_ids:%d;last', $this->id); - } - } - - /** - * get the Profile_list object by the - * given tagger and with given tag - * - * @param integer $tagger the id of the creator profile - * @param integer $tag the tag - * - * @return integer count - */ - - public static function getByTaggerAndTag($tagger, $tag) - { - $ptag = Profile_list::pkeyGet(array('tagger' => $tagger, 'tag' => $tag)); - return $ptag; - } - - /** - * create a profile_list record for a tag, tagger pair - * if it doesn't exist, return it. - * - * @param integer $tagger the tagger - * @param string $tag the tag - * @param string $description description - * @param boolean $private protected or not - * - * @return Profile_list the people tag object - */ - - public static function ensureTag($tagger, $tag, $description = null, $private = false) - { - $ptag = Profile_list::getByTaggerAndTag($tagger, $tag); - - if (empty($ptag->id)) { - $args = array( - 'tag' => $tag, - 'tagger' => $tagger, - 'description' => $description, - 'private' => $private - ); - - $new_tag = Profile_list::saveNew($args); - - return $new_tag; - } - return $ptag; - } - - /** - * get the maximum number of characters - * that can be used in the description of - * a people tag. - * - * determined by $config['peopletag']['desclimit'] - * if not set, falls back to $config['site']['textlimit'] - * - * @return integer maximum number of characters - */ - - public static function maxDescription() - { - $desclimit = common_config('peopletag', 'desclimit'); - // null => use global limit (distinct from 0!) - if (is_null($desclimit)) { - $desclimit = common_config('site', 'textlimit'); - } - return $desclimit; - } - - /** - * check if the length of given text exceeds - * character limit. - * - * @param string $desc the description - * - * @return boolean is the descripition too long? - */ - - public static function descriptionTooLong($desc) - { - $desclimit = self::maxDescription(); - return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit)); - } - - /** - * save a new people tag, this should be always used - * since it makes uri, homeurl, created and modified - * timestamps and performs checks. - * - * @param array $fields an array with fields and their values - * - * @return mixed Profile_list on success, false on fail - */ - public static function saveNew(array $fields) - { - extract($fields); - - $ptag = new Profile_list(); - - $ptag->query('START TRANSACTION'); - - if (empty($tagger)) { - // TRANS: Server exception saving new tag without having a tagger specified. - throw new Exception(_('No tagger specified.')); - } - - if (empty($tag)) { - // TRANS: Server exception saving new tag without having a tag specified. - throw new Exception(_('No tag specified.')); - } - - if (empty($mainpage)) { - $mainpage = null; - } - - if (empty($uri)) { - // fill in later... - $uri = null; - } - - if (empty($mainpage)) { - $mainpage = null; - } - - if (empty($description)) { - $description = null; - } - - if (empty($private)) { - $private = false; - } - - $ptag->tagger = $tagger; - $ptag->tag = $tag; - $ptag->description = $description; - $ptag->private = $private; - $ptag->uri = $uri; - $ptag->mainpage = $mainpage; - $ptag->created = common_sql_now(); - $ptag->modified = common_sql_now(); - - $result = $ptag->insert(); - - if (!$result) { - common_log_db_error($ptag, 'INSERT', __FILE__); - // TRANS: Server exception saving new tag. - throw new ServerException(_('Could not create profile tag.')); - } - - if (!isset($uri) || empty($uri)) { - $orig = clone($ptag); - $ptag->uri = common_local_url('profiletagbyid', array('id' => $ptag->id, 'tagger_id' => $ptag->tagger)); - $result = $ptag->update($orig); - if (!$result) { - common_log_db_error($ptag, 'UPDATE', __FILE__); - // TRANS: Server exception saving new tag. - throw new ServerException(_('Could not set profile tag URI.')); - } - } - - if (!isset($mainpage) || empty($mainpage)) { - $orig = clone($ptag); - $user = User::getKV('id', $ptag->tagger); - if (!empty($user)) { - $ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'nickname' => $user->getNickname())); - } else { - $ptag->mainpage = $uri; // assume this is a remote peopletag and the uri works - } - - $result = $ptag->update($orig); - if (!$result) { - common_log_db_error($ptag, 'UPDATE', __FILE__); - // TRANS: Server exception saving new tag. - throw new ServerException(_('Could not set profile tag mainpage.')); - } - } - return $ptag; - } - - /** - * get all items at given cursor position for api - * - * @param callback $fn a function that takes the following arguments in order: - * $offset, $limit, $since_id, $max_id - * and returns a Profile_list object after making the DB query - * @param array $args arguments required for $fn - * @param integer $cursor the cursor - * @param integer $count max. number of results - * - * Algorithm: - * - if cursor is 0, return empty list - * - if cursor is -1, get first 21 items, next_cursor = 20th prev_cursor = 0 - * - if cursor is +ve get 22 consecutive items before starting at cursor - * - return items[1..20] if items[0] == cursor else return items[0..21] - * - prev_cursor = items[1] - * - next_cursor = id of the last item being returned - * - * - if cursor is -ve get 22 consecutive items after cursor starting at cursor - * - return items[1..20] - * - * @returns array (array (mixed items), int next_cursor, int previous_cursor) - */ - - // XXX: This should be in Memcached_DataObject... eventually - - public static function getAtCursor($fn, array $args, $cursor, $count = 20) - { - $items = array(); - - $since_id = 0; - $max_id = 0; - $next_cursor = 0; - $prev_cursor = 0; - - if ($cursor > 0) { - // if cursor is +ve fetch $count+2 items before cursor starting at cursor - $max_id = $cursor; - $fn_args = array_merge($args, array(0, $count+2, 0, $max_id)); - $list = call_user_func_array($fn, $fn_args); - while ($list->fetch()) { - $items[] = clone($list); - } - - if ((isset($items[0]->cursor) && $items[0]->cursor == $cursor) || - $items[0]->id == $cursor) { - array_shift($items); - $prev_cursor = isset($items[0]->cursor) ? - -$items[0]->cursor : -$items[0]->id; - } else { - if (count($items) > $count+1) { - array_shift($items); - } - // this means the cursor item has been deleted, check to see if there are more - $fn_args = array_merge($args, array(0, 1, $cursor)); - $more = call_user_func($fn, $fn_args); - if (!$more->fetch() || empty($more)) { - // no more items. - $prev_cursor = 0; - } else { - $prev_cursor = isset($items[0]->cursor) ? - -$items[0]->cursor : -$items[0]->id; - } - } - - if (count($items)==$count+1) { - // this means there is a next page. - $next = array_pop($items); - $next_cursor = isset($next->cursor) ? - $items[$count-1]->cursor : $items[$count-1]->id; - } - } elseif ($cursor < -1) { - // if cursor is -ve fetch $count+2 items created after -$cursor-1 - $cursor = abs($cursor); - $since_id = $cursor-1; - - $fn_args = array_merge($args, array(0, $count+2, $since_id)); - $list = call_user_func_array($fn, $fn_args); - while ($list->fetch()) { - $items[] = clone($list); - } - - $end = count($items)-1; - if ((isset($items[$end]->cursor) && $items[$end]->cursor == $cursor) || - $items[$end]->id == $cursor) { - array_pop($items); - $next_cursor = isset($items[$end-1]->cursor) ? - $items[$end-1]->cursor : $items[$end-1]->id; - } else { - $next_cursor = isset($items[$end]->cursor) ? - $items[$end]->cursor : $items[$end]->id; - if ($end > $count) { - // excess item - array_pop($items); - } - - // check if there are more items for next page - $fn_args = array_merge($args, array(0, 1, 0, $cursor)); - $more = call_user_func_array($fn, $fn_args); - if (!$more->fetch() || empty($more)) { - $next_cursor = 0; - } - } - - if (count($items) == $count+1) { - // this means there is a previous page. - $prev = array_shift($items); - $prev_cursor = isset($prev->cursor) ? - -$items[0]->cursor : -$items[0]->id; - } - } elseif ($cursor == -1) { - $fn_args = array_merge($args, array(0, $count+1)); - $list = call_user_func_array($fn, $fn_args); - - while ($list->fetch()) { - $items[] = clone($list); - } - - if (count($items)==$count+1) { - $next = array_pop($items); - if (isset($next->cursor)) { - $next_cursor = $items[$count-1]->cursor; - } else { - $next_cursor = $items[$count-1]->id; - } - } - } - return array($items, $next_cursor, $prev_cursor); - } - - /** - * save a collection of people tags into the cache - * - * @param string $ckey cache key - * @param Profile_list &$tag the results to store - * @param integer $offset offset for slicing results - * @param integer $limit maximum number of results - * - * @return boolean success - */ - - public static function setCache($ckey, &$tag, $offset = 0, $limit = null) - { - $cache = Cache::instance(); - if (empty($cache)) { - return false; - } - $str = ''; - $tags = array(); - while ($tag->fetch()) { - $str .= $tag->tagger . ':' . $tag->tag . ';'; - $tags[] = clone($tag); - } - $str = substr($str, 0, -1); - if ($offset>=0 && !is_null($limit)) { - $tags = array_slice($tags, $offset, $limit); - } - - $tag = new ArrayWrapper($tags); - - return self::cacheSet($ckey, $str); - } - - /** - * get people tags from the cache - * - * @param string $ckey cache key - * @param integer $offset offset for slicing - * @param integer $limit limit - * - * @return Profile_list results - */ - - public static function getCached($ckey, $offset = 0, $limit = null) - { - $keys_str = self::cacheGet($ckey); - if ($keys_str === false) { - return false; - } - - $pairs = explode(';', $keys_str); - $keys = array(); - foreach ($pairs as $pair) { - $keys[] = explode(':', $pair); - } - - if ($offset>=0 && !is_null($limit)) { - $keys = array_slice($keys, $offset, $limit); - } - return self::getByKeys($keys); - } - - /** - * get Profile_list objects from the database - * given their (tag, tagger) key pairs. - * - * @param array $keys array of array(tagger, tag) - * - * @return Profile_list results - */ - - public static function getByKeys(array $keys) - { - $cache = Cache::instance(); - - if (!empty($cache)) { - $tags = array(); - - foreach ($keys as $key) { - $t = Profile_list::getByTaggerAndTag($key[0], $key[1]); - if (!empty($t)) { - $tags[] = $t; - } - } - return new ArrayWrapper($tags); - } else { - $tag = new Profile_list(); - if (empty($keys)) { - //if no IDs requested, just return the tag object - return $tag; - } - - $pairs = array(); - foreach ($keys as $key) { - $pairs[] = '(' . $key[0] . ', "' . $key[1] . '")'; - } - - $tag->whereAdd('(tagger, tag) in (' . implode(', ', $pairs) . ')'); - - $tag->find(); - - $temp = array(); - - while ($tag->fetch()) { - $temp[$tag->tagger.'-'.$tag->tag] = clone($tag); - } - - $wrapped = array(); - - foreach ($keys as $key) { - $id = $key[0].'-'.$key[1]; - if (array_key_exists($id, $temp)) { - $wrapped[] = $temp[$id]; - } - } - - return new ArrayWrapper($wrapped); - } - } - - public function insert() - { - $result = parent::insert(); - if ($result) { - self::blow('profile:lists:%d', $this->tagger); - } - return $result; - } -} diff --git a/src/Entity/Profile_prefs.php b/src/Entity/Profile_prefs.php deleted file mode 100644 index ff1e92dacf..0000000000 --- a/src/Entity/Profile_prefs.php +++ /dev/null @@ -1,172 +0,0 @@ -. - -/** - * Data class for Profile preferences - * - * @category Data - * @package GNUsocial - * @author Mikael Nordfeldth - * @copyright 2013 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Profile_prefs extends Managed_DataObject -{ - public $__table = 'profile_prefs'; // table name - public $profile_id; // int(4) primary_key not_null - public $namespace; // varchar(191) not_null - public $topic; // varchar(191) not_null - public $data; // text - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - 'fields' => array( - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'), - 'namespace' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'), - 'topic' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'), - 'data' => array('type' => 'blob', 'description' => 'topic data, may be anything'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('profile_id', 'namespace', 'topic'), - 'foreign keys' => array( - 'profile_prefs_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - ); - } - - public static function getNamespacePrefs(Profile $profile, $namespace, array $topic = []) - { - if (empty($topic)) { - $prefs = new Profile_prefs(); - $prefs->profile_id = $profile->getID(); - $prefs->namespace = $namespace; - $prefs->find(); - } else { - $prefs = self::pivotGet('profile_id', $profile->getID(), array('namespace'=>$namespace, 'topic'=>$topic)); - } - - if (empty($prefs->N)) { - throw new NoResultException($prefs); - } - - return $prefs; - } - - public static function getNamespace(Profile $profile, $namespace, array $topic = []) - { - $prefs = self::getNamespacePrefs($profile, $namespace, $topic); - return $prefs->fetchAll(); - } - - public static function getAll(Profile $profile) - { - try { - $prefs = self::listFind('profile_id', array($profile->getID())); - } catch (NoResultException $e) { - return array(); - } - - $list = array(); - while ($prefs->fetch()) { - if (!isset($list[$prefs->namespace])) { - $list[$prefs->namespace] = array(); - } - $list[$prefs->namespace][$prefs->topic] = $prefs->data; - } - return $list; - } - - public static function getTopic(Profile $profile, $namespace, $topic) - { - return Profile_prefs::getByPK(array('profile_id' => $profile->getID(), - 'namespace' => $namespace, - 'topic' => $topic)); - } - - public static function getData(Profile $profile, $namespace, $topic, $def = null) - { - try { - $pref = self::getTopic($profile, $namespace, $topic); - } catch (NoResultException $e) { - if ($def === null) { - // If no default value was set, continue the exception. - throw $e; - } - // If there was a default value, return that. - return $def; - } - return $pref->data; - } - - public static function getConfigData(Profile $profile, $namespace, $topic) - { - try { - $data = self::getData($profile, $namespace, $topic); - } catch (NoResultException $e) { - $data = common_config($namespace, $topic); - } - return $data; - } - - /* - * Sets a profile preference based on Profile, namespace and topic - * - * @param Profile $profile Which profile this is for - * @param string $namespace Under which namespace (pluginname etc.) - * @param string $topic Preference name (think key in key-val store) - * @param string $data Data to be put into preference storage, null means delete - * - * @return true if changes are made, false if no action taken - * @throws ServerException if preference could not be saved - */ - public static function setData(Profile $profile, $namespace, $topic, $data = null) - { - try { - $pref = self::getTopic($profile, $namespace, $topic); - if (is_null($data)) { - $pref->delete(); - } else { - $orig = clone($pref); - $pref->data = DB_DataObject_Cast::blob($data); - $pref->update($orig); - } - return true; - } catch (NoResultException $e) { - if (is_null($data)) { - return false; // No action taken - } - } - - $pref = new Profile_prefs(); - $pref->profile_id = $profile->getID(); - $pref->namespace = $namespace; - $pref->topic = $topic; - $pref->data = DB_DataObject_Cast::blob($data); - $pref->created = common_sql_now(); - - if ($pref->insert() === false) { - throw new ServerException('Could not save profile preference.'); - } - return true; - } -} diff --git a/src/Entity/Profile_role.php b/src/Entity/Profile_role.php deleted file mode 100644 index 38c846a8dc..0000000000 --- a/src/Entity/Profile_role.php +++ /dev/null @@ -1,78 +0,0 @@ -. - -/* - * Table Definition for profile_role - * - * @copyright 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Profile_role extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'profile_role'; // table name - public $profile_id; // int(4) primary_key not_null - public $role; // varchar(32) primary_key not_null - public $created; // datetime() - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'account having the role'), - 'role' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'string representing the role'), - 'created' => array('type' => 'datetime', 'description' => 'date the role was granted'), - ), - 'primary key' => array('profile_id', 'role'), - 'foreign keys' => array( - 'profile_role_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - 'indexes' => array('profile_role_role_created_profile_id_idx' => array('role', 'created', 'profile_id')), - ); - } - - const OWNER = 'owner'; - const MODERATOR = 'moderator'; - const ADMINISTRATOR = 'administrator'; - const SANDBOXED = 'sandboxed'; - const SILENCED = 'silenced'; - const DELETED = 'deleted'; // Pending final deletion of notices... - - public static function isValid($role) - { - // @fixme could probably pull this from class constants - $known = array(self::OWNER, - self::MODERATOR, - self::ADMINISTRATOR, - self::SANDBOXED, - self::SILENCED); - return in_array($role, $known); - } - - public static function isSettable($role) - { - $allowedRoles = array('administrator', 'moderator'); - return self::isValid($role) && in_array($role, $allowedRoles); - } -} diff --git a/src/Entity/Profile_tag.php b/src/Entity/Profile_tag.php deleted file mode 100644 index c9cdec3b3d..0000000000 --- a/src/Entity/Profile_tag.php +++ /dev/null @@ -1,363 +0,0 @@ -. - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for profile_tag - */ -class Profile_tag extends Managed_DataObject -{ - public $__table = 'profile_tag'; // table name - public $tagger; // int(4) primary_key not_null - public $tagged; // int(4) primary_key not_null - public $tag; // varchar(64) primary_key not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - - 'fields' => array( - 'tagger' => array('type' => 'int', 'not null' => true, 'description' => 'user making the tag'), - 'tagged' => array('type' => 'int', 'not null' => true, 'description' => 'profile tagged'), - 'tag' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'hash tag associated with this notice'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date the tag was added'), - ), - 'primary key' => array('tagger', 'tagged', 'tag'), - 'foreign keys' => array( - 'profile_tag_tagger_fkey' => array('profile', array('tagger' => 'id')), - 'profile_tag_tagged_fkey' => array('profile', array('tagged' => 'id')), - ), - 'indexes' => array( - 'profile_tag_modified_tagged_idx' => array('modified', 'tagged'), - 'profile_tag_tagger_tag_idx' => array('tagger', 'tag'), - 'profile_tag_tagged_idx' => array('tagged'), - ), - ); - } - - public function links() - { - return array('tagger,tag' => 'profile_list:tagger,tag'); - } - - public function getMeta() - { - return Profile_list::pkeyGet(array('tagger' => $this->tagger, 'tag' => $this->tag)); - } - - public static function getSelfTagsArray(Profile $target) - { - return self::getTagsArray($target->getID(), $target->getID(), $target); - } - - public static function setSelfTags(Profile $target, array $newtags, array $privacy = []) - { - return self::setTags($target->getID(), $target->getID(), $newtags, $privacy); - } - - public static function getTags($tagger, $tagged, $auth_user = null) - { - $profile_list = new Profile_list(); - $include_priv = 1; - - if (!($auth_user instanceof User || - $auth_user instanceof Profile) || - ($auth_user->id !== $tagger)) { - $profile_list->private = false; - $include_priv = 0; - } - - $key = sprintf('profile_tag:tagger_tagged_privacy:%d-%d-%d', $tagger, $tagged, $include_priv); - $tags = Profile_list::getCached($key); - if ($tags !== false) { - return $tags; - } - - $qry = 'select profile_list.* from profile_list left join '. - 'profile_tag on (profile_list.tag = profile_tag.tag and '. - 'profile_list.tagger = profile_tag.tagger) where '. - 'profile_tag.tagger = %d and profile_tag.tagged = %d '; - $qry = sprintf($qry, $tagger, $tagged); - - if (!$include_priv) { - $qry .= ' AND profile_list.private IS NOT TRUE'; - } - - $profile_list->query($qry); - - Profile_list::setCache($key, $profile_list); - - return $profile_list; - } - - public static function getTagsArray($tagger, $tagged, Profile $scoped = null) - { - $ptag = new Profile_tag(); - - $qry = sprintf( - 'SELECT profile_tag.tag '. - 'FROM profile_tag INNER JOIN profile_list '. - ' ON (profile_tag.tagger = profile_list.tagger ' . - ' and profile_tag.tag = profile_list.tag) ' . - 'WHERE profile_tag.tagger = %d ' . - 'AND profile_tag.tagged = %d ', - $tagger, - $tagged - ); - - if (!$scoped instanceof Profile || $scoped->getID() !== $tagger) { - $qry .= 'AND profile_list.private IS NOT TRUE'; - } - - $tags = array(); - - $ptag->query($qry); - - while ($ptag->fetch()) { - $tags[] = $ptag->tag; - } - - return $tags; - } - - public static function setTags($tagger, $tagged, array $newtags, array $privacy = []) - { - $newtags = array_unique($newtags); - $oldtags = self::getTagsArray($tagger, $tagged, Profile::getByID($tagger)); - - $ptag = new Profile_tag(); - - // Delete stuff that's in old and not in new - - $to_delete = array_diff($oldtags, $newtags); - - // Insert stuff that's in new and not in old - - $to_insert = array_diff($newtags, $oldtags); - - foreach ($to_delete as $deltag) { - self::unTag($tagger, $tagged, $deltag); - } - - foreach ($to_insert as $instag) { - $private = isset($privacy[$instag]) ? $privacy[$instag] : false; - self::setTag($tagger, $tagged, $instag, null, $private); - } - return true; - } - - # set a single tag - public static function setTag($tagger, $tagged, $tag, $desc=null, $private = false) - { - $ptag = Profile_tag::pkeyGet(array('tagger' => $tagger, - 'tagged' => $tagged, - 'tag' => $tag)); - - # if tag already exists, return it - if ($ptag instanceof Profile_tag) { - return $ptag; - } - - $tagger_profile = Profile::getByID($tagger); - $tagged_profile = Profile::getByID($tagged); - - if (Event::handle('StartTagProfile', array($tagger_profile, $tagged_profile, $tag))) { - if (!$tagger_profile->canTag($tagged_profile)) { - // TRANS: Client exception thrown trying to set a tag for a user that cannot be tagged. - throw new ClientException(_('You cannot tag this user.')); - } - - $tags = new Profile_list(); - $tags->tagger = $tagger; - $count = (int) $tags->count('distinct tag'); - - if ($count >= common_config('peopletag', 'maxtags')) { - // TRANS: Client exception thrown trying to set more tags than allowed. - throw new ClientException(sprintf( - _('You already have created %d or more tags ' . - 'which is the maximum allowed number of tags. ' . - 'Try using or deleting some existing tags.'), - common_config('peopletag', 'maxtags') - )); - } - - $plist = new Profile_list(); - $plist->query('START TRANSACTION'); - - $profile_list = Profile_list::ensureTag($tagger, $tag, $desc, $private); - - if ($profile_list->taggedCount() >= common_config('peopletag', 'maxpeople')) { - // TRANS: Client exception thrown when trying to add more people than allowed to a list. - throw new ClientException(sprintf( - _('You already have %1$d or more people in list %2$s, ' . - 'which is the maximum allowed number. ' . - 'Try unlisting others first.'), - common_config('peopletag', 'maxpeople'), - $tag - )); - } - - $newtag = new Profile_tag(); - - $newtag->tagger = $tagger; - $newtag->tagged = $tagged; - $newtag->tag = $tag; - - $result = $newtag->insert(); - - if (!$result) { - common_log_db_error($newtag, 'INSERT', __FILE__); - $plist->query('ROLLBACK'); - return false; - } - - try { - $plist->query('COMMIT'); - Event::handle('EndTagProfile', array($newtag)); - } catch (Exception $e) { - $newtag->delete(); - $profile_list->delete(); - throw $e; - } - - $profile_list->taggedCount(true); - self::blowCaches($tagger, $tagged); - } - - return $newtag; - } - - public static function unTag($tagger, $tagged, $tag) - { - $ptag = Profile_tag::pkeyGet(array('tagger' => $tagger, - 'tagged' => $tagged, - 'tag' => $tag)); - if (!$ptag) { - return true; - } - - if (Event::handle('StartUntagProfile', array($ptag))) { - $orig = clone($ptag); - $result = $ptag->delete(); - if ($result === false) { - common_log_db_error($this, 'DELETE', __FILE__); - return false; - } - Event::handle('EndUntagProfile', array($orig)); - $profile_list = Profile_list::pkeyGet(array('tag' => $tag, 'tagger' => $tagger)); - if (!empty($profile_list)) { - $profile_list->taggedCount(true); - } - self::blowCaches($tagger, $tagged); - return true; - } - } - - // @fixme: move this to Profile_list? - public static function cleanup($profile_list) - { - $ptag = new Profile_tag(); - $ptag->tagger = $profile_list->tagger; - $ptag->tag = $profile_list->tag; - $ptag->find(); - - while ($ptag->fetch()) { - if (Event::handle('StartUntagProfile', array($ptag))) { - $orig = clone($ptag); - $result = $ptag->delete(); - if (!$result) { - common_log_db_error($this, 'DELETE', __FILE__); - } - Event::handle('EndUntagProfile', array($orig)); - } - } - } - - // move a tag! - public static function moveTag($orig, $new) - { - $tags = new Profile_tag(); - $result = $tags->query(sprintf( - <<<'END' - UPDATE profile_tag - SET tag = %1$s, tagger = %2$s, modified = CURRENT_TIMESTAMP - WHERE tag = %3$s AND tagger = %4$s - END, - $tags->_quote($new->tag), - $tags->_quote($new->tagger), - $tags->_quote($orig->tag), - $tags->_quote($orig->tagger) - )); - - if ($result === false) { - common_log_db_error($tags, 'UPDATE', __FILE__); - throw new Exception('Could not move Profile_tag, see db log for details.'); - } - return $result; - } - - public static function blowCaches($tagger, $tagged) - { - foreach (array(0, 1) as $perm) { - self::blow(sprintf('profile_tag:tagger_tagged_privacy:%d-%d-%d', $tagger, $tagged, $perm)); - } - return true; - } - - // Return profiles with a given tag - public static function getTagged($tagger, $tag) - { - $profile = new Profile(); - $profile->query('SELECT profile.* ' . - 'FROM profile JOIN profile_tag ' . - 'ON profile.id = profile_tag.tagged ' . - 'WHERE profile_tag.tagger = ' . $profile->escape($tagger) . ' ' . - "AND profile_tag.tag = '" . $profile->escape($tag) . "' "); - $tagged = []; - while ($profile->fetch()) { - $tagged[] = clone($profile); - } - return true; - } - - public function insert() - { - $result = parent::insert(); - if ($result) { - self::blow( - 'profile_list:tagged_count:%d:%s', - $this->tagger, - $this->tag - ); - } - return $result; - } - - public function delete($useWhere = false) - { - $result = parent::delete($useWhere); - if ($result !== false) { - self::blow( - 'profile_list:tagged_count:%d:%s', - $this->tagger, - $this->tag - ); - } - return $result; - } -} diff --git a/src/Entity/Profile_tag_subscription.php b/src/Entity/Profile_tag_subscription.php deleted file mode 100644 index 2eb4fed083..0000000000 --- a/src/Entity/Profile_tag_subscription.php +++ /dev/null @@ -1,158 +0,0 @@ -. - -/** - * Table Definition for profile_tag_subscription - */ - -defined('GNUSOCIAL') || die(); - -class Profile_tag_subscription extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'profile_tag_subscription'; // table name - public $profile_tag_id; // int(4) not_null - public $profile_id; // int(4) not_null - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'profile_tag_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile_tag'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), - - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('profile_tag_id', 'profile_id'), - 'foreign keys' => array( - 'profile_tag_subscription_profile_tag_id_fkey' => array('profile_list', array('profile_tag_id' => 'id')), - 'profile_tag_subscription_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - 'indexes' => array( - 'profile_tag_subscription_profile_id_created_profile_tag_id_idx' => array('profile_id', 'created', 'profile_tag_id'), - ), - ); - } - - public static function add($peopletag, $profile) - { - if ($peopletag->private) { - return false; - } - - if (Event::handle('StartSubscribePeopletag', array($peopletag, $profile))) { - $args = array('profile_tag_id' => $peopletag->id, - 'profile_id' => $profile->id); - $existing = Profile_tag_subscription::pkeyGet($args); - if (!empty($existing)) { - return $existing; - } - - $sub = new Profile_tag_subscription(); - $sub->profile_tag_id = $peopletag->id; - $sub->profile_id = $profile->id; - $sub->created = common_sql_now(); - - $result = $sub->insert(); - - if (!$result) { - common_log_db_error($sub, 'INSERT', __FILE__); - // TRANS: Exception thrown when inserting a list subscription in the database fails. - throw new Exception(_('Adding list subscription failed.')); - } - - $ptag = Profile_list::getKV('id', $peopletag->id); - $ptag->subscriberCount(true); - - Event::handle('EndSubscribePeopletag', array($peopletag, $profile)); - return $ptag; - } - } - - public static function remove($peopletag, $profile) - { - $sub = Profile_tag_subscription::pkeyGet(array('profile_tag_id' => $peopletag->id, - 'profile_id' => $profile->id)); - - if (empty($sub)) { - // silence is golden? - return true; - } - - if (Event::handle('StartUnsubscribePeopletag', array($peopletag, $profile))) { - $result = $sub->delete(); - - if (!$result) { - common_log_db_error($sub, 'DELETE', __FILE__); - // TRANS: Exception thrown when deleting a list subscription from the database fails. - throw new Exception(_('Removing list subscription failed.')); - } - - $peopletag->subscriberCount(true); - - Event::handle('EndUnsubscribePeopletag', array($peopletag, $profile)); - return true; - } - } - - // called if a tag gets deleted / made private - public static function cleanup($profile_list) - { - $subs = new self(); - $subs->profile_tag_id = $profile_list->id; - $subs->find(); - - while ($subs->fetch()) { - $profile = Profile::getKV('id', $subs->profile_id); - Event::handle('StartUnsubscribePeopletag', array($profile_list, $profile)); - // Delete anyway - $subs->delete(); - Event::handle('StartUnsubscribePeopletag', array($profile_list, $profile)); - } - } - - public function insert() - { - $result = parent::insert(); - if ($result) { - self::blow( - 'profile_list:subscriber_count:%d', - $this->profile_tag_id - ); - } - return $result; - } - - public function delete($useWhere = false) - { - $result = parent::delete($useWhere); - if ($result !== false) { - self::blow( - 'profile_list:subscriber_count:%d', - $this->profile_tag_id - ); - } - return $result; - } -} diff --git a/src/Entity/QueueItem.php b/src/Entity/QueueItem.php new file mode 100644 index 0000000000..0a38076c81 --- /dev/null +++ b/src/Entity/QueueItem.php @@ -0,0 +1,59 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for a Queue Item + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class QueueItem +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'queue_item', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], + 'frame' => ['type' => 'blob', 'not null' => true, 'description' => 'data: object reference or opaque string'], + 'transport' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'queue for what? "email", "xmpp", "sms", "irc", ...'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'claimed' => ['type' => 'datetime', 'description' => 'date this item was claimed'], + ], + 'primary key' => ['id'], + 'indexes' => [ + 'queue_item_created_idx' => ['created'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Queue_item.php b/src/Entity/Queue_item.php deleted file mode 100644 index 6401325cf2..0000000000 --- a/src/Entity/Queue_item.php +++ /dev/null @@ -1,122 +0,0 @@ -. - -/** - * Table Definition for queue_item - */ - -defined('GNUSOCIAL') || die(); - -class Queue_item extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'queue_item'; // table name - public $id; // int(4) primary_key not_null - public $frame; // blob not_null - public $transport; // varchar(32) - public $created; // datetime() - public $claimed; // datetime() - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), - 'frame' => array('type' => 'blob', 'not null' => true, 'description' => 'data: object reference or opaque string'), - 'transport' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'queue for what? "email", "xmpp", "sms", "irc", ...'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'claimed' => array('type' => 'datetime', 'description' => 'date this item was claimed'), - ), - 'primary key' => array('id'), - 'indexes' => array( - 'queue_item_created_id_idx' => array('created', 'id'), - ), - ); - } - - /** - * @param mixed $transports name of a single queue or array of queues to pull from - * If not specified, checks all queues in the system. - */ - public static function top($transports = null, array $ignored_transports = []) - { - $qi = new Queue_item(); - if ($transports) { - if (is_array($transports)) { - $qi->whereAddIn( - 'transport', - $transports, - $qi->columnType('transport') - ); - } else { - $qi->transport = $transports; - } - } - if (!empty($ignored_transports)) { - $qi->whereAddIn( - '!transport', - $ignored_transports, - $qi->columnType('transport') - ); - } - $qi->whereAdd('claimed IS NULL'); - $qi->orderBy('created, id'); - - $qi->limit(1); - - $cnt = $qi->find(true); - - if ($cnt) { - // XXX: potential race condition - // can we force it to only update if claimed is still null - // (or old)? - common_log(LOG_INFO, 'claiming queue item id = ' . $qi->getID() . ' for transport ' . $qi->transport); - $orig = clone($qi); - $qi->claimed = common_sql_now(); - $result = $qi->update($orig); - if ($result) { - common_log(LOG_DEBUG, 'claim succeeded.'); - return $qi; - } else { - common_log(LOG_ERR, 'claim of queue item id= ' . $qi->getID() . ' for transport ' . $qi->transport . ' failed.'); - } - } - unset($qi); - return null; - } - - /** - * Release a claimed item. - */ - public function releaseClaim() - { - // @fixme Consider $this->sqlValue('NULL') - $ret = $this->query(sprintf( - 'UPDATE queue_item SET claimed = NULL WHERE id = %d', - $this->getID() - )); - - if ($ret) { - $this->claimed = null; - $this->encache(); - } - } -} diff --git a/src/Entity/RelatedGroup.php b/src/Entity/RelatedGroup.php new file mode 100644 index 0000000000..52377ba0b9 --- /dev/null +++ b/src/Entity/RelatedGroup.php @@ -0,0 +1,59 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for related groups + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class RelatedGroup +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'related_group', + // @fixme description for related_group? + 'fields' => [ + 'group_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'], + 'related_group_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + ], + 'primary key' => ['group_id', 'related_group_id'], + 'foreign keys' => [ + 'related_group_group_id_fkey' => ['user_group', ['group_id' => 'id']], + 'related_group_related_group_id_fkey' => ['user_group', ['related_group_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Related_group.php b/src/Entity/Related_group.php deleted file mode 100644 index ff51e6cd20..0000000000 --- a/src/Entity/Related_group.php +++ /dev/null @@ -1,55 +0,0 @@ -. - -/** - * Table Definition for related_group - */ - -defined('GNUSOCIAL') || die(); - -class Related_group extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'related_group'; // table name - public $group_id; // int(4) primary_key not_null - public $related_group_id; // int(4) primary_key not_null - public $created; // datetime() - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - // @fixme description for related_group? - 'fields' => array( - 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'), - 'related_group_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - ), - 'primary key' => array('group_id', 'related_group_id'), - 'foreign keys' => array( - 'related_group_group_id_fkey' => array('user_group', array('group_id' => 'id')), - 'related_group_related_group_id_fkey' => array('user_group', array('related_group_id' => 'id')), - ), - 'indexes' => array( - 'related_group_related_group_id_idx' => array('related_group_id'), - ), - ); - } -} diff --git a/src/Entity/RememberMe.php b/src/Entity/RememberMe.php new file mode 100644 index 0000000000..bb781e54dc --- /dev/null +++ b/src/Entity/RememberMe.php @@ -0,0 +1,57 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user remember me + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class RememberMe +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'remember_me', + 'fields' => [ + 'code' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'], + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'user who is logged in'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['code'], + 'foreign keys' => [ + 'remember_me_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Remember_me.php b/src/Entity/Remember_me.php deleted file mode 100644 index f7cbee5f58..0000000000 --- a/src/Entity/Remember_me.php +++ /dev/null @@ -1,53 +0,0 @@ -. - -/** - * Table Definition for remember_me - */ - -defined('GNUSOCIAL') || die(); - -class Remember_me extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'remember_me'; // table name - public $code; // varchar(32) primary_key not_null - public $user_id; // int(4) not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'), - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who is logged in'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('code'), - 'foreign keys' => array( - 'remember_me_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - 'indexes' => array( - 'remember_me_user_id_idx' => array('user_id'), - ), - ); - } -} diff --git a/src/Entity/Reply.php b/src/Entity/Reply.php index 9baa0989c9..91f016450e 100644 --- a/src/Entity/Reply.php +++ b/src/Entity/Reply.php @@ -1,85 +1,65 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for reply + * Entity for Notice reply + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Reply extends Managed_DataObject +class Reply { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'reply'; // table name - public $notice_id; // int(4) primary_key not_null - public $profile_id; // int(4) primary_key not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $replied_id; // int(4) + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() + public static function schemaDef(): array { - return array( - 'fields' => array( - 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice that is the reply'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile replied to'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - 'replied_id' => array('type' => 'int', 'description' => 'notice replied to (not used, see notice.reply_to)'), - ), - 'primary key' => array('notice_id', 'profile_id'), - 'foreign keys' => array( - 'reply_notice_id_fkey' => array('notice', array('notice_id' => 'id')), - 'reply_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - 'indexes' => array( - 'reply_profile_id_idx' => array('profile_id'), - 'reply_replied_id_idx' => array('replied_id'), - 'reply_profile_id_modified_notice_id_idx' => array('profile_id', 'modified', 'notice_id') - ), - ); - } - - /** - * Wrapper for record insertion to update related caches - */ - public function insert() - { - $result = parent::insert(); - - if ($result) { - self::blow('reply:stream:%d', $this->profile_id); - } - - return $result; - } - - public static function stream( - $user_id, - $offset = 0, - $limit = NOTICES_PER_PAGE, - $since_id = 0, - $max_id = 0 - ) { - // FIXME: Use some other method to get Profile::current() in order - // to avoid confusion between background processing and session user. - $stream = new ReplyNoticeStream($user_id, Profile::current()); - return $stream->getNotices($offset, $limit, $since_id, $max_id); + return [ + 'name' => 'reply', + 'fields' => [ + 'notice_id' => ['type' => 'int', 'not null' => true, 'description' => 'notice that is the reply'], + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'profile replied to'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + 'replied_id' => ['type' => 'int', 'description' => 'notice replied to (not used, see notice.reply_to)'], + ], + 'primary key' => ['notice_id', 'profile_id'], + 'foreign keys' => [ + 'reply_notice_id_fkey' => ['notice', ['notice_id' => 'id']], + 'reply_profile_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + 'reply_notice_id_idx' => ['notice_id'], + 'reply_profile_id_idx' => ['profile_id'], + 'reply_replied_id_idx' => ['replied_id'], + 'reply_profile_id_modified_notice_id_idx' => ['profile_id', 'modified', 'notice_id'], + ], + ]; } } diff --git a/src/Entity/Safe_DataObject.php b/src/Entity/Safe_DataObject.php deleted file mode 100644 index 4473603724..0000000000 --- a/src/Entity/Safe_DataObject.php +++ /dev/null @@ -1,309 +0,0 @@ -. - -/* - * @copyright 2010 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -/** - * Extended DB_DataObject to improve a few things: - * - free global resources from destructor - * - remove bogus global references from serialized objects - * - don't leak memory when loading already-used .ini files - * (eg when using the same schema on thousands of databases) - */ -class Safe_DataObject extends GS_DataObject -{ - /** - * Destructor to free global memory resources associated with - * this data object when it's unset or goes out of scope. - * DB_DataObject doesn't do this yet by itself. - */ - - public function __destruct() - { - $this->free(); - if (method_exists('DB_DataObject', '__destruct')) { - parent::__destruct(); - } - } - - /** - * Magic function called at clone() time. - * - * We use this to drop connection with some global resources. - * This supports the fairly common pattern where individual - * items being read in a loop via a single object are cloned - * for individual processing, then fall out of scope when the - * loop comes around again. - * - * As that triggers the destructor, we want to make sure that - * the original object doesn't have its database result killed. - * It will still be freed properly when the original object - * gets destroyed. - */ - public function __clone() - { - $this->_DB_resultid = false; - } - - /** - * Magic function called at serialize() time. - * - * We use this to drop a couple process-specific references - * from DB_DataObject which can cause trouble in future - * processes. - * - * @return array of variable names to include in serialization. - */ - public function __sleep() - { - $vars = array_keys(get_object_vars($this)); - $skip = array('_DB_resultid', '_link_loaded'); - return array_diff($vars, $skip); - } - - /** - * Magic function called at unserialize() time. - * - * Clean out some process-specific variables which might - * be floating around from a previous process's cached - * objects. - * - * Old cached objects may still have them. - */ - public function __wakeup() - { - // Refers to global state info from a previous process. - // Clear this out so we don't accidentally break global - // state in *this* process. - $this->_DB_resultid = null; - // We don't have any local DBO refs, so clear these out. - $this->_link_loaded = false; - } - - /** - * Magic function called when someone attempts to call a method - * that doesn't exist. DB_DataObject uses this to implement - * setters and getters for fields, but neglects to throw an error - * when you just misspell an actual method name. This leads to - * silent failures which can cause all kinds of havoc. - * - * @param string $method - * @param array $params - * @return mixed - * @throws Exception - */ - public function __call($method, $params) - { - $return = null; - // Yes, that's _call with one underscore, which does the - // actual implementation. - if ($this->_call($method, $params, $return)) { - return $return; - } else { - // Low level exception. No need for i18n as discussed with Brion. - throw new Exception('Call to undefined method ' . - get_class($this) . '::' . $method); - } - } - - /** - * Work around memory-leak bugs... - * Had to copy-paste the whole function in order to patch a couple lines of it. - * Would be nice if this code was better factored. - * - * @param optional string name of database to assign / read - * @param optional array structure of database, and keys - * @param optional array table links - * - * @access public - * @return true or PEAR:error on wrong paramenters.. or false if no file exists.. - * or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename) - */ - public function databaseStructure() - { - global $_DB_DATAOBJECT; - - if (!empty($args = func_get_args())) { - if (count($args) == 1) { - // this returns all the tables and their structure.. - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug( - 'Loading Generator as databaseStructure called with args', - 1 - ); - } - - $x = new DB_DataObject; - $x->_database = $args[0]; - $this->_connect(); - $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; - - $tables = $DB->getListOf('tables'); - class_exists('DB_DataObject_Generator') ? '' : - require_once 'DB/DataObject/Generator.php'; - - foreach ($tables as $table) { - $y = new DB_DataObject_Generator; - $y->fillTableSchema($x->_database, $table); - } - return $_DB_DATAOBJECT['INI'][$x->_database]; - } else { - $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ? - $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1]; - - if (isset($args[1])) { - $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ? - $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2]; - } - return true; - } - } - if (!$this->_database) { - $this->_connect(); - } - - // loaded already? - if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) { - - // database loaded - but this is table is not available.. - if ( - empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()]) - && !empty($_DB_DATAOBJECT['CONFIG']['proxy']) - ) { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug('Loading Generator to fetch Schema', 1); - } - class_exists('DB_DataObject_Generator') ? '' : - require_once 'DB/DataObject/Generator.php'; - - - $x = new DB_DataObject_Generator; - $x->fillTableSchema($this->_database, $this->tableName()); - } - return true; - } - - if (empty($_DB_DATAOBJECT['CONFIG'])) { - self::_loadConfig(); - } - - // if you supply this with arguments, then it will take those - // as the database and links array... - - $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ? - array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") : - array() ; - - if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) { - $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ? - $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] : - explode(PATH_SEPARATOR, $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]); - } - - /* BEGIN CHANGED FROM UPSTREAM */ - $_DB_DATAOBJECT['INI'][$this->_database] = $this->parseIniFiles($schemas); - /* END CHANGED FROM UPSTREAM */ - - // now have we loaded the structure.. - - if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->tableName()])) { - return true; - } - // - if not try building it.. - if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) { - class_exists('DB_DataObject_Generator') ? '' : - require_once 'DB/DataObject/Generator.php'; - - $x = new DB_DataObject_Generator; - $x->fillTableSchema($this->_database, $this->tableName()); - // should this fail!!!??? - return true; - } - $this->debug( - "Can't find database schema: {$this->_database}/{$this->tableName()}\n" - . 'in links file data: ' - . print_r($_DB_DATAOBJECT['INI'], true), - 'databaseStructure', - 5 - ); - // we have to die here!! - it causes chaos if we don't (including looping forever!) - // Low level exception. No need for i18n as discussed with Brion. - $this->raiseError( - 'Unable to load schema for database and table ' - . '(turn debugging up to 5 for full error message)', - DB_DATAOBJECT_ERROR_INVALIDARGS, - PEAR_ERROR_DIE - ); - return false; - } - - /** For parseIniFiles */ - protected static $iniCache = array(); - - /** - * When switching site configurations, DB_DataObject was loading its - * .ini files over and over, leaking gobs of memory. - * This refactored helper function uses a local cache of .ini files - * to minimize the leaks. - * - * @param array of .ini file names $schemas - * @return array - */ - protected function parseIniFiles(array $schemas) - { - $key = implode("|", $schemas); - if (!isset(Safe_DataObject::$iniCache[$key])) { - $data = array(); - foreach ($schemas as $ini) { - if (file_exists($ini) && is_file($ini)) { - $data = array_merge($data, parse_ini_file($ini, true)); - - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - if (!is_readable($ini)) { - $this->debug( - "ini file is not readable: {$ini}", - 'databaseStructure', - 1 - ); - } else { - $this->debug( - "Loaded ini file: {$ini}", - 'databaseStructure', - 1 - ); - } - } - } else { - if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { - $this->debug( - "Missing ini file: {$ini}", - 'databaseStructure', - 1 - ); - } - } - } - Safe_DataObject::$iniCache[$key] = $data; - } - - return Safe_DataObject::$iniCache[$key]; - } -} diff --git a/src/Entity/SchemaVersion.php b/src/Entity/SchemaVersion.php new file mode 100644 index 0000000000..203f29e15a --- /dev/null +++ b/src/Entity/SchemaVersion.php @@ -0,0 +1,55 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for the Schema Version + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class SchemaVersion +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'schema_version', + 'description' => 'To avoid checking database structure all the time, we store a checksum of the expected schema info for each table here. If it has not changed since the last time we checked the table, we can leave it as is.', + 'fields' => [ + 'table_name' => ['type' => 'varchar', 'length' => '64', 'not null' => true, 'description' => 'Table name'], + 'checksum' => ['type' => 'varchar', 'length' => '64', 'not null' => true, 'description' => 'Checksum of schema array; a mismatch indicates we should check the table more thoroughly.'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['table_name'], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Schema_version.php b/src/Entity/Schema_version.php deleted file mode 100644 index fe89bb88fb..0000000000 --- a/src/Entity/Schema_version.php +++ /dev/null @@ -1,48 +0,0 @@ -. - -/** - * Table Definition for schema_version - */ - -defined('GNUSOCIAL') || die(); - -class Schema_version extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'schema_version'; // table name - public $table_name; // varchar(64) primary_key not_null - public $checksum; // varchar(128) not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'description' => 'To avoid checking database structure all the time, we store a checksum of the expected schema info for each table here. If it has not changed since the last time we checked the table, we can leave it as is.', - 'fields' => array( - 'table_name' => array('type' => 'varchar', 'length' => '64', 'not null' => true, 'description' => 'Table name'), - 'checksum' => array('type' => 'varchar', 'length' => '128', 'not null' => true, 'description' => 'Checksum of schema array; a mismatch indicates we should check the table more thoroughly.'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('table_name'), - ); - } -} diff --git a/src/Entity/Session.php b/src/Entity/Session.php index 1fc3c43a4b..d8ea561303 100644 --- a/src/Entity/Session.php +++ b/src/Entity/Session.php @@ -1,84 +1,59 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for session + * Entity for Superclass representing a saved session as it exists in the database. * + * @category DB * @package GNUsocial - * @author Evan Prodromou - * @author Brion Vibber - * @author Mikael Nordfeldth - * @author Sorokin Alexei + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org * @author Diogo Cordeiro * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -/** - * Superclass representing a saved session as it exists in the database. - * - * @copyright 2019 Free Software Foundation, Inc http://www.fsf.org - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ -class Session extends Managed_DataObject +class Session { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'session'; // table name - public $id; // varchar(32) primary_key not_null - public $session_data; // text() - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - /** - * Returns an array describing how the session is stored in the database. - * - * @return array - */ - public static function schemaDef() + public static function schemaDef(): array { return [ 'fields' => [ - 'id' => ['type' => 'varchar', 'length' => 128, 'not null' => true, 'description' => 'session ID'], + 'id' => ['type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'session ID'], 'session_data' => ['type' => 'text', 'description' => 'session data'], - 'created' => ['type' => 'datetime', 'description' => 'date this record was created'], - 'modified' => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], ], 'primary key' => ['id'], - 'indexes' => [ + 'indexes' => [ 'session_modified_idx' => ['modified'], ], ]; } - - /** - * New code should NOT call this function. - * Dummy function for backwards compatibility with older plugins like Qvitter. - * Stuff to do before the request teardown. - * - * @return void - */ - public static function cleanup() - { - session_write_close(); - } } diff --git a/src/Entity/SmsCarrier.php b/src/Entity/SmsCarrier.php new file mode 100644 index 0000000000..4e5c91aec5 --- /dev/null +++ b/src/Entity/SmsCarrier.php @@ -0,0 +1,59 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for SMS carriers + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class SmsCarrier +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'sms_carrier', + 'fields' => [ + 'id' => ['type' => 'int', 'not null' => true, 'description' => 'primary key for SMS carrier'], + 'name' => ['type' => 'varchar', 'length' => 64, 'description' => 'name of the carrier'], + 'email_pattern' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'sprintf pattern for making an email address from a phone number'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'sms_carrier_name_key' => ['name'], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Sms_carrier.php b/src/Entity/Sms_carrier.php deleted file mode 100644 index 6b30b9265e..0000000000 --- a/src/Entity/Sms_carrier.php +++ /dev/null @@ -1,59 +0,0 @@ -. - -/** - * Table Definition for sms_carrier - */ - -defined('GNUSOCIAL') || die(); - -class Sms_carrier extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'sms_carrier'; // table name - public $id; // int(4) primary_key not_null - public $name; // varchar(64) unique_key - public $email_pattern; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public function toEmailAddress($sms) - { - return sprintf($this->email_pattern, $sms); - } - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'int', 'not null' => true, 'description' => 'primary key for SMS carrier'), - 'name' => array('type' => 'varchar', 'length' => 64, 'description' => 'name of the carrier'), - 'email_pattern' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'sprintf pattern for making an email address from a phone number'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'sms_carrier_name_key' => array('name'), - ), - ); - } -} diff --git a/src/Entity/Status_network.php b/src/Entity/Status_network.php deleted file mode 100644 index f6812c175c..0000000000 --- a/src/Entity/Status_network.php +++ /dev/null @@ -1,388 +0,0 @@ -. - -/** - * Table Definition for status_network - * - * @copyright 2009 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class Status_network extends Safe_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'status_network'; // table name - public $site_id; // int(4) primary_key not_null - public $nickname; // varchar(64) unique_key not_null - public $hostname; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $pathname; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $dbhost; // varchar(191) not 255 because utf8mb4 takes more space - public $dbuser; // varchar(191) not 255 because utf8mb4 takes more space - public $dbpass; // varchar(191) not 255 because utf8mb4 takes more space - public $dbname; // varchar(191) not 255 because utf8mb4 takes more space - public $sitename; // varchar(191) not 255 because utf8mb4 takes more space - public $theme; // varchar(191) not 255 because utf8mb4 takes more space - public $logo; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() not_null - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* Static get */ - public static function getKV($k, $v = null) - { - // TODO: This must probably be turned into a non-static call - $i = DB_DataObject::staticGet('Status_network', $k, $v); - - // Don't use local process cache; if we're fetching multiple - // times it's because we're reloading it in a long-running - // process; we need a fresh copy! - global $_DB_DATAOBJECT; - unset($_DB_DATAOBJECT['CACHE']['status_network']); - return $i; - } - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - // XXX: made public so Status_network_tag can eff with it - public static $cache = null; - public static $cacheInitialized = false; - public static $base = null; - public static $wildcard = null; - - /** - * @param string $dbhost - * @param string $dbuser - * @param string $dbpass - * @param string $dbname - * @param array $servers memcached servers to use for caching config info - */ - public static function setupDB( - $dbhost, - $dbuser, - $dbpass, - $dbname, - array $servers - ) { - global $config; - - $config['db']['database_'.$dbname] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname"; - $config['db']['ini_'.$dbname] = INSTALLDIR.'/classes/status_network.ini'; - - foreach (array('status_network', 'status_network_tag', 'unavailable_status_network') as $table) { - $config['db']['table_'.$table] = $dbname; - } - - if (class_exists('Memcache')) { - self::$cache = new Memcache(); - - // If we're a parent command-line process we need - // to be able to close out the connection after - // forking, so disable persistence. - // - // We'll turn it back on again the second time - // through which will either be in a child process, - // or a single-process script which is switching - // configurations. - $persist = php_sapi_name() != 'cli' || self::$cacheInitialized; - if (!is_array($servers)) { - $servers = array($servers); - } - foreach ($servers as $server) { - $parts = explode(':', $server); - $server = $parts[0]; - if (count($parts) > 1) { - $port = $parts[1]; - } else { - $port = 11211; - } - self::$cache->addServer($server, $port, $persist); - } - self::$cacheInitialized = true; - } - - self::$base = $dbname; - } - - public static function cacheKey($k, $v) - { - return 'gnusocial:' . self::$base . ':status_network:'.$k.':'.$v; - } - - public static function memGet($k, $v) - { - if (!self::$cache) { - return self::getKV($k, $v); - } - - $ck = self::cacheKey($k, $v); - - $sn = self::$cache->get($ck); - - if (empty($sn)) { - $sn = self::getKV($k, $v); - if (!empty($sn)) { - self::$cache->set($ck, clone($sn)); - } - } - - return $sn; - } - - public function decache() - { - if (self::$cache) { - $keys = array('nickname', 'hostname', 'pathname'); - foreach ($keys as $k) { - $ck = self::cacheKey($k, $this->$k); - self::$cache->delete($ck); - } - } - } - - public function update($dataObject = false) - { - if (is_object($dataObject)) { - // might be different keys - $dataObject->decache(); - } - return parent::update($dataObject); - } - - /** - * DB_DataObject doesn't allow updating keys (even non-primary) - */ - public function updateKeys(&$orig) - { - $this->_connect(); - foreach (array('hostname', 'pathname') as $k) { - if (strcmp($this->$k, $orig->$k) != 0) { - $parts[] = $k . ' = ' . $this->_quote($this->$k); - } - } - if (count($parts) == 0) { - // No changes - return true; - } - - $toupdate = implode(', ', $parts); - - $table = common_database_tablename($this->tableName()); - $qry = 'UPDATE ' . $table . ' SET ' . $toupdate . - ' WHERE nickname = ' . $this->_quote($this->nickname); - $orig->decache(); - $result = $this->query($qry); - $this->decache(); - - return $result; - } - - public function delete($useWhere = false) - { - // while we still have the values! - $this->decache(); - return parent::delete($useWhere); - } - - /** - * @param string $servername hostname - * @param string $wildcard hostname suffix to match wildcard config - * @return mixed Status_network or null - */ - public static function getFromHostname($servername, $wildcard) - { - $sn = null; - if (0 == strncasecmp(strrev($wildcard), strrev($servername), strlen($wildcard))) { - // special case for exact match - if (0 == strcasecmp($servername, $wildcard)) { - $sn = self::memGet('nickname', ''); - } else { - $parts = explode('.', $servername); - $sn = self::memGet('nickname', strtolower($parts[0])); - } - } else { - $sn = self::memGet('hostname', strtolower($servername)); - - if (empty($sn)) { - // Try for a no-www address - if (0 == strncasecmp($servername, 'www.', 4)) { - $sn = self::memGet('hostname', strtolower(substr($servername, 4))); - } - } - } - return $sn; - } - - /** - * @param string $servername hostname - * @param string $pathname URL base path - * @param string $wildcard hostname suffix to match wildcard config - */ - public static function setupSite($servername, $pathname, $wildcard) - { - global $config; - - $sn = null; - - // XXX I18N, probably not crucial for hostnames - // XXX This probably needs a tune up - $sn = self::getFromHostname($servername, $wildcard); - - if (!empty($sn)) { - - // Redirect to the right URL - - if (!empty($sn->hostname) && - empty($_SERVER['HTTPS']) && - 0 != strcasecmp($sn->hostname, $servername)) { - $sn->redirectTo('http://'.$sn->hostname.$_SERVER['REQUEST_URI']); - } elseif ( - !empty($_SERVER['HTTPS']) - && strcasecmp($sn->hostname, $servername) !== 0 - && strcasecmp($sn->nickname . '.' . $wildcard, $servername) !== 0 - ) { - $sn->redirectTo( - "https://{$sn->nickname}.{$wildcard}{$_SERVER['REQUEST_URI']}" - ); - } - - $dbhost = (empty($sn->dbhost)) ? 'localhost' : $sn->dbhost; - $dbuser = (empty($sn->dbuser)) ? $sn->nickname : $sn->dbuser; - $dbpass = $sn->dbpass; - $dbname = (empty($sn->dbname)) ? $sn->nickname : $sn->dbname; - - $config['db']['database'] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname"; - - $config['site']['name'] = $sn->sitename; - $config['site']['nickname'] = $sn->nickname; - - self::$wildcard = $wildcard; - - $config['site']['wildcard'] =& self::$wildcard; - - if (!empty($sn->hostname)) { - $config['site']['server'] = $sn->hostname; - } - - if (!empty($sn->theme)) { - $config['site']['theme'] = $sn->theme; - } - if (!empty($sn->logo)) { - $config['site']['logo'] = $sn->logo; - } - - return $sn; - } else { - return null; - } - } - - // Code partially mooked from http://www.richler.de/en/php-redirect/ - // (C) 2006 by Heiko Richler http://www.richler.de/ - // LGPL - - public function redirectTo($destination) - { - $old = 'http'. - (($_SERVER['HTTPS'] == 'on') ? 'S' : ''). - '://'. - $_SERVER['HTTP_HOST']. - $_SERVER['REQUEST_URI']. - $_SERVER['QUERY_STRING']; - if ($old == $destination) { // this would be a loop! - // error_log(...) ? - return false; - } - - http_response_code(301); - header("Location: {$destination}"); - - echo "{$destination}\n"; - - exit; - } - - public function getServerName() - { - if (!empty($this->hostname)) { - return $this->hostname; - } else { - return $this->nickname . '.' . self::$wildcard; - } - } - - /** - * Return site meta-info tags as an array - * @return array of strings - */ - public function getTags() - { - return Status_network_tag::getTags($this->site_id); - } - - /** - * Save a given set of tags - * @param array tags - * @fixme only add/remove differentials - */ - public function setTags(array $tags) - { - $this->clearTags(); - foreach ($tags as $tag) { - if (!empty($tag)) { - $snt = new Status_network_tag(); - $snt->site_id = $this->site_id; - $snt->tag = $tag; - $snt->created = common_sql_now(); - - $id = $snt->insert(); - if (!$id) { - // TRANS: Exception thrown when a tag cannot be saved. - throw new Exception(_("Unable to save tag.")); - } - } - } - - return true; - } - - public function clearTags() - { - $tag = new Status_network_tag(); - $tag->site_id = $this->site_id; - - if ($tag->find()) { - while ($tag->fetch()) { - $tag->delete(); - } - } - - $tag->free(); - } - - /** - * Check if this site record has a particular meta-info tag attached. - * @param string $tag - * @return bool - */ - public function hasTag($tag) - { - return in_array($tag, $this->getTags()); - } -} diff --git a/src/Entity/Status_network_tag.php b/src/Entity/Status_network_tag.php deleted file mode 100644 index ba73e7ba1b..0000000000 --- a/src/Entity/Status_network_tag.php +++ /dev/null @@ -1,144 +0,0 @@ -. - */ - -if (!defined('STATUSNET')) { exit(1); } - -class Status_network_tag extends Safe_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'status_network_tag'; // table name - public $site_id; // int(4) primary_key not_null - public $tag; // varchar(64) primary_key not_null - public $created; // datetime() not_null default_0000-00-00%2000%3A00%3A00 - - - function __construct() - { - global $config; - global $_DB_DATAOBJECT; - - $sn = new Status_network(); - $sn->_connect(); - - $config['db']['table_'. $this->tableName()] = $sn->_database; - - $this->_connect(); - } - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - /* Static get */ - static function getKV($k,$v=null) - { - // TODO: This probably has to be converted to a non-static call - $i = DB_DataObject::staticGet('Status_network_tag',$k,$v); - - // Don't use local process cache; if we're fetching multiple - // times it's because we're reloading it in a long-running - // process; we need a fresh copy! - global $_DB_DATAOBJECT; - unset($_DB_DATAOBJECT['CACHE']['status_network_tag']); - return $i; - } - - static function pkeyGet($kv) - { - return Memcached_DataObject::pkeyGetClass('Status_network_tag', $kv); - } - - /** - * Fetch the (possibly cached) tag entries for the given site id. - * Uses status_network's cache settings. - * - * @param string $site_id - * @return array of strings - */ - static function getTags($site_id) - { - $key = 'status_network_tags:' . $site_id; - if (Status_network::$cache) { - $packed = Status_network::$cache->get($key); - if (is_string($packed)) { - if ($packed == '') { - return array(); - } else { - return explode('|', $packed); - } - } - } - - $result = array(); - - $tags = new Status_network_tag(); - $tags->site_id = $site_id; - if ($tags->find()) { - while ($tags->fetch()) { - $result[] = $tags->tag; - } - } - - if (Status_network::$cache) { - $packed = implode('|', $result); - Status_network::$cache->set($key, $packed, 0, 3600); - } - - return $result; - } - - /** - * Drop the cached tag entries for this site. - * Needed after inserting/deleting a tag entry. - */ - function decache() - { - $key = 'status_network_tags:' . $this->site_id; - if (Status_network::$cache || Status_network::$cacheInitialized) { - // FIXME: this was causing errors, so I'm hiding them. - // I'm a big chicken and lazy. - @Status_network::$cache->delete($key); - } - } - - function insert() - { - $ret = parent::insert(); - $this->decache(); - return $ret; - } - - function delete($useWhere=false) - { - $this->decache(); - return parent::delete($useWhere); - } - - static function withTag($tag) - { - $snt = new Status_network_tag(); - - $snt->tag = $tag; - - $snt->find(); - - return $snt; - } -} diff --git a/src/Entity/Subscription.php b/src/Entity/Subscription.php index 0737d3b486..da017f7d5e 100644 --- a/src/Entity/Subscription.php +++ b/src/Entity/Subscription.php @@ -1,31 +1,41 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * @copyright 2008, 2009 StatusNet, Inc. + * Entity for subscription + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for subscription - */ -class Subscription extends Managed_DataObject +class Subscription { +<<<<<<< HEAD const CACHE_WINDOW = 201; const FORCE = true; @@ -446,5 +456,36 @@ class Subscription extends Managed_DataObject public function getUri() { return $this->uri ?: self::newUri($this->getSubscriber(), $this->getSubscribed(), $this->created); +======= + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'subscription', + 'fields' => [ + 'subscriber' => ['type' => 'int', 'not null' => true, 'description' => 'profile listening'], + 'subscribed' => ['type' => 'int', 'not null' => true, 'description' => 'profile being listened to'], + 'jabber' => ['type' => 'bool', 'default' => true, 'description' => 'deliver jabber messages'], + 'sms' => ['type' => 'bool', 'default' => true, 'description' => 'deliver sms messages'], + 'token' => ['type' => 'varchar', 'length' => 191, 'description' => 'authorization token'], + 'secret' => ['type' => 'varchar', 'length' => 191, 'description' => 'token secret'], + 'uri' => ['type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['subscriber', 'subscribed'], + 'unique keys' => [ + 'subscription_uri_key' => ['uri'], + ], + 'indexes' => [ + 'subscription_subscriber_idx' => ['subscriber', 'created'], + 'subscription_subscribed_idx' => ['subscribed', 'created'], + 'subscription_token_idx' => ['token'], + ], + ]; +>>>>>>> e4b74a6aaf ([DATABASE] Extracted schemaDef method from old files and refactored onto new files) } } diff --git a/src/Entity/SubscriptionQueue.php b/src/Entity/SubscriptionQueue.php new file mode 100644 index 0000000000..6f495033a6 --- /dev/null +++ b/src/Entity/SubscriptionQueue.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for Subscription queue + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class SubscriptionQueue +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'subscription_queue', + 'description' => 'Holder for subscription requests awaiting moderation.', + 'fields' => [ + 'subscriber' => ['type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'], + 'subscribed' => ['type' => 'int', 'not null' => true, 'description' => 'remote or local profile being subscribed to'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + ], + 'primary key' => ['subscriber', 'subscribed'], + 'indexes' => [ + 'subscription_queue_subscriber_created_idx' => ['subscriber', 'created'], + 'subscription_queue_subscribed_created_idx' => ['subscribed', 'created'], + ], + 'foreign keys' => [ + 'subscription_queue_subscriber_fkey' => ['profile', ['subscriber' => 'id']], + 'subscription_queue_subscribed_fkey' => ['profile', ['subscribed' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Subscription_queue.php b/src/Entity/Subscription_queue.php deleted file mode 100644 index b09a635676..0000000000 --- a/src/Entity/Subscription_queue.php +++ /dev/null @@ -1,124 +0,0 @@ -. - -/** - * Table Definition for subscription_queue - */ - -defined('GNUSOCIAL') || die(); - -class Subscription_queue extends Managed_DataObject -{ - public $__table = 'subscription_queue'; // table name - public $subscriber; - public $subscribed; - public $created; - - public static function schemaDef() - { - return array( - 'description' => 'Holder for subscription requests awaiting moderation.', - 'fields' => array( - 'subscriber' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile making the request'), - 'subscribed' => array('type' => 'int', 'not null' => true, 'description' => 'remote or local profile being subscribed to'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - ), - 'primary key' => array('subscriber', 'subscribed'), - 'indexes' => array( - 'subscription_queue_subscriber_created_idx' => array('subscriber', 'created'), - 'subscription_queue_subscribed_created_idx' => array('subscribed', 'created'), - ), - 'foreign keys' => array( - 'subscription_queue_subscriber_fkey' => array('profile', array('subscriber' => 'id')), - 'subscription_queue_subscribed_fkey' => array('profile', array('subscribed' => 'id')), - ) - ); - } - - public static function saveNew(Profile $subscriber, Profile $subscribed) - { - if (self::exists($subscriber, $subscribed)) { - throw new AlreadyFulfilledException(_('This subscription request is already in progress.')); - } - $rq = new Subscription_queue(); - $rq->subscriber = $subscriber->id; - $rq->subscribed = $subscribed->id; - $rq->created = common_sql_now(); - $rq->insert(); - return $rq; - } - - public static function exists(Profile $subscriber, Profile $other) - { - $sub = Subscription_queue::pkeyGet(array('subscriber' => $subscriber->getID(), - 'subscribed' => $other->getID())); - return ($sub instanceof Subscription_queue); - } - - public static function getSubQueue(Profile $subscriber, Profile $other) - { - // This is essentially a pkeyGet but we have an object to return in NoResultException - $sub = new Subscription_queue(); - $sub->subscriber = $subscriber->id; - $sub->subscribed = $other->id; - if (!$sub->find(true)) { - throw new NoResultException($sub); - } - return $sub; - } - - /** - * Complete a pending subscription, as we've got approval of some sort. - * - * @return Subscription - */ - public function complete() - { - $subscriber = Profile::getKV('id', $this->subscriber); - $subscribed = Profile::getKV('id', $this->subscribed); - try { - $sub = Subscription::start($subscriber, $subscribed, Subscription::FORCE); - $this->delete(); - } catch (AlreadyFulfilledException $e) { - common_debug('Tried to start a subscription which already existed.'); - } - return $sub; - } - - /** - * Cancel an outstanding subscription request to the other profile. - */ - public function abort() - { - $subscriber = Profile::getKV('id', $this->subscriber); - $subscribed = Profile::getKV('id', $this->subscribed); - if (Event::handle('StartCancelSubscription', array($subscriber, $subscribed))) { - $this->delete(); - Event::handle('EndCancelSubscription', array($subscriber, $subscribed)); - } - } - - /** - * Send notifications via email etc to group administrators about - * this exciting new pending moderation queue item! - */ - public function notify() - { - $other = Profile::getKV('id', $this->subscriber); - $listenee = User::getKV('id', $this->subscribed); - mail_subscribe_pending_notify_profile($listenee, $other); - } -} diff --git a/src/Entity/Token.php b/src/Entity/Token.php index 196379701c..fc8146309a 100644 --- a/src/Entity/Token.php +++ b/src/Entity/Token.php @@ -1,62 +1,64 @@ . + +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for token + * Entity for User token + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -defined('GNUSOCIAL') || die(); - -class Token extends Managed_DataObject +class Token { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE BEGIN - public $__table = 'token'; // table name - public $consumer_key; // varchar(191) primary_key not_null not 255 because utf8mb4 takes more space - public $tok; // char(32) primary_key not_null - public $secret; // char(32) not_null - public $type; // tinyint(1) not_null - public $state; // tinyint(1) - public $verifier; // varchar(191) not 255 because utf8mb4 takes more space - public $verified_callback; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // AUTOCODE END - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - public static function schemaDef() + public static function schemaDef(): array { - return array( + return [ + 'name' => 'token', 'description' => 'OAuth token record', - 'fields' => array( - 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'), - 'tok' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'identifying value'), - 'secret' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'secret value'), - 'type' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 0, 'description' => 'request or access'), - 'state' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'for requests, 0 = initial, 1 = authorized, 2 = used'), - 'verifier' => array('type' => 'varchar', 'length' => 191, 'description' => 'verifier string for OAuth 1.0a'), - 'verified_callback' => array('type' => 'varchar', 'length' => 191, 'description' => 'verified callback URL for OAuth 1.0a'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('consumer_key', 'tok'), - 'foreign keys' => array( - 'token_consumer_key_fkey' => array('consumer', array('consumer_key' => 'consumer_key')), - ), - ); + 'fields' => [ + 'consumer_key' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'], + 'tok' => ['type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'identifying value'], + 'secret' => ['type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'secret value'], + 'type' => ['type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 0, 'description' => 'request or access'], + 'state' => ['type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'for requests, 0 = initial, 1 = authorized, 2 = used'], + 'verifier' => ['type' => 'varchar', 'length' => 191, 'description' => 'verifier string for OAuth 1.0a'], + 'verified_callback' => ['type' => 'varchar', 'length' => 191, 'description' => 'verified callback URL for OAuth 1.0a'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['consumer_key', 'tok'], + 'foreign keys' => [ + 'token_consumer_key_fkey' => ['consumer', ['consumer_key' => 'consumer_key']], + ], + ]; } } diff --git a/src/Entity/UnavailableStatusNetwork.php b/src/Entity/UnavailableStatusNetwork.php new file mode 100644 index 0000000000..9fe4e6e839 --- /dev/null +++ b/src/Entity/UnavailableStatusNetwork.php @@ -0,0 +1,57 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity that Keeps a list of unavailable status network names + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class UnavailableStatusNetwork +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'unavailable_status_network', + 'description' => 'An unavailable status network nickname', + 'fields' => [ + 'nickname' => ['type' => 'varchar', + 'length' => 64, + 'not null' => true, 'description' => 'nickname not to use', ], + 'created' => ['type' => 'datetime', + 'not null' => true, 'default' => '0000-00-00 00:00:00', ], + ], + 'primary key' => ['nickname'], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/Unavailable_status_network.php b/src/Entity/Unavailable_status_network.php deleted file mode 100644 index 415acaa002..0000000000 --- a/src/Entity/Unavailable_status_network.php +++ /dev/null @@ -1,63 +0,0 @@ -. - -/** - * Data class for unavailable status networks - * - * @category Data - * @package GNUsocial - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -/** - * Keeps a list of unavailable status network names - * - * @category Data - * @package GNUsocial - * @author Evan Prodromou - * @copyright 2011 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - * - * @see Managed_DataObject - */ -class Unavailable_status_network extends Managed_DataObject -{ - public $__table = 'unavailable_status_network'; // table name - - public $nickname; // varchar(64) UUID - public $created; // datetime() - - /** - * The One True Thingy that must be defined and declared. - */ - public static function schemaDef() - { - return array( - 'description' => 'An unavailable status network nickname', - 'fields' => array( - 'nickname' => array('type' => 'varchar', - 'length' => 64, - 'not null' => true, 'description' => 'nickname not to use'), - 'created' => array('type' => 'datetime'), - ), - 'primary key' => array('nickname'), - ); - } -} diff --git a/src/Entity/User.php b/src/Entity/User.php index 00299ee0ee..5fdd461828 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -1,67 +1,50 @@ . -defined('GNUSOCIAL') || die(); +/* {{{ License + * This file is part of GNU social - https://www.gnu.org/software/social + * + * GNU social is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNU social is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with GNU social. If not, see . + }}} */ + +namespace App\Entity; /** - * Table Definition for user + * Entity for users + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ - -class User extends Managed_DataObject +class User { - const SUBSCRIBE_POLICY_OPEN = 0; - const SUBSCRIBE_POLICY_MODERATE = 1; + // AUTOCODE BEGIN - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ + // AUTOCODE END - public $__table = 'user'; // table name - public $id; // int(4) primary_key not_null - public $nickname; // varchar(64) unique_key - public $password; // text - public $email; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $incomingemail; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $emailnotifysub; // bool default_true - public $emailnotifyfav; // tinyint(1) default_null - public $emailnotifynudge; // bool default_true - public $emailnotifymsg; // bool default_true - public $emailnotifyattn; // bool default_true - public $language; // varchar(50) - public $timezone; // varchar(50) - public $emailpost; // bool default_true - public $sms; // varchar(64) unique_key - public $carrier; // int(4) - public $smsnotify; // bool default_false - public $smsreplies; // bool default_false - public $smsemail; // varchar(191) not 255 because utf8mb4 takes more space - public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $autosubscribe; // bool default_false - public $subscribe_policy; // tinyint(1) - public $urlshorteningservice; // varchar(50) default_ur1.ca - public $private_stream; // bool default_false - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() + public static function schemaDef(): array { - return array( + return [ + 'name' => 'user', 'description' => 'local users', +<<<<<<< HEAD 'fields' => array( 'id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'nickname or username, duped in profile'), @@ -1079,5 +1062,51 @@ class User extends Managed_DataObject public function setPref($namespace, $topic, $data) { return $this->getProfile()->setPref($namespace, $topic, $data); +======= + 'fields' => [ + 'id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'], + 'nickname' => ['type' => 'varchar', 'length' => 64, 'description' => 'nickname or username, duped in profile'], + 'password' => ['type' => 'varchar', 'length' => 191, 'description' => 'salted password, can be null for OpenID users'], + 'email' => ['type' => 'varchar', 'length' => 191, 'description' => 'email address for password recovery etc.'], + 'incomingemail' => ['type' => 'varchar', 'length' => 191, 'description' => 'email address for post-by-email'], + 'emailnotifysub' => ['type' => 'bool', 'default' => true, 'description' => 'Notify by email of subscriptions'], + 'emailnotifyfav' => ['type' => 'int', 'size' => 'tiny', 'default' => null, 'description' => 'Notify by email of favorites'], + 'emailnotifynudge' => ['type' => 'bool', 'default' => true, 'description' => 'Notify by email of nudges'], + 'emailnotifymsg' => ['type' => 'bool', 'default' => true, 'description' => 'Notify by email of direct messages'], + 'emailnotifyattn' => ['type' => 'bool', 'default' => true, 'description' => 'Notify by email of @-replies'], + 'language' => ['type' => 'varchar', 'length' => 50, 'description' => 'preferred language'], + 'timezone' => ['type' => 'varchar', 'length' => 50, 'description' => 'timezone'], + 'emailpost' => ['type' => 'bool', 'default' => true, 'description' => 'Post by email'], + 'sms' => ['type' => 'varchar', 'length' => 64, 'description' => 'sms phone number'], + 'carrier' => ['type' => 'int', 'description' => 'foreign key to sms_carrier'], + 'smsnotify' => ['type' => 'bool', 'default' => false, 'description' => 'whether to send notices to SMS'], + 'smsreplies' => ['type' => 'bool', 'default' => false, 'description' => 'whether to send notices to SMS on replies'], + 'smsemail' => ['type' => 'varchar', 'length' => 191, 'description' => 'built from sms and carrier'], + 'uri' => ['type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'], + 'autosubscribe' => ['type' => 'bool', 'default' => false, 'description' => 'automatically subscribe to users who subscribe to us'], + 'subscribe_policy' => ['type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => '0 = anybody can subscribe; 1 = require approval'], + 'urlshorteningservice' => ['type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'], + 'private_stream' => ['type' => 'bool', 'default' => false, 'description' => 'whether to limit all notices to followers only'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'user_nickname_key' => ['nickname'], + 'user_email_key' => ['email'], + 'user_incomingemail_key' => ['incomingemail'], + 'user_sms_key' => ['sms'], + 'user_uri_key' => ['uri'], + ], + 'foreign keys' => [ + 'user_id_fkey' => ['profile', ['id' => 'id']], + 'user_carrier_fkey' => ['sms_carrier', ['carrier' => 'id']], + ], + 'indexes' => [ + 'user_created_idx' => ['created'], + 'user_smsemail_idx' => ['smsemail'], + ], + ]; +>>>>>>> e4b74a6aaf ([DATABASE] Extracted schemaDef method from old files and refactored onto new files) } } diff --git a/src/Entity/UserGroup.php b/src/Entity/UserGroup.php new file mode 100644 index 0000000000..03afdcc9e1 --- /dev/null +++ b/src/Entity/UserGroup.php @@ -0,0 +1,83 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for groups a user is in + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class UserGroup +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'user_group', + 'fields' => [ + 'id' => ['type' => 'serial', 'not null' => true, 'description' => 'unique identifier'], + 'profile_id' => ['type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'], + + 'nickname' => ['type' => 'varchar', 'length' => 64, 'description' => 'nickname for addressing'], + 'fullname' => ['type' => 'varchar', 'length' => 191, 'description' => 'display name'], + 'homepage' => ['type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'], + 'description' => ['type' => 'text', 'description' => 'group description'], + 'location' => ['type' => 'varchar', 'length' => 191, 'description' => 'related physical location, if any'], + + 'original_logo' => ['type' => 'varchar', 'length' => 191, 'description' => 'original size logo'], + 'homepage_logo' => ['type' => 'varchar', 'length' => 191, 'description' => 'homepage (profile) size logo'], + 'stream_logo' => ['type' => 'varchar', 'length' => 191, 'description' => 'stream-sized logo'], + 'mini_logo' => ['type' => 'varchar', 'length' => 191, 'description' => 'mini logo'], + + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + + 'uri' => ['type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'], + 'mainpage' => ['type' => 'varchar', 'length' => 191, 'description' => 'page for group info to link to'], + 'join_policy' => ['type' => 'int', 'size' => 'tiny', 'description' => '0=open; 1=requires admin approval'], + 'force_scope' => ['type' => 'int', 'size' => 'tiny', 'description' => '0=never,1=sometimes,-1=always'], + ], + 'primary key' => ['id'], + 'unique keys' => [ + 'user_group_uri_key' => ['uri'], + // when it's safe and everyone's run upgrade.php 'user_profile_id_key' => array('profile_id'), + ], + 'foreign keys' => [ + 'user_group_id_fkey' => ['profile', ['profile_id' => 'id']], + ], + 'indexes' => [ + 'user_group_nickname_idx' => ['nickname'], + 'user_group_profile_id_idx' => ['profile_id'], //make this unique in future + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/UserImPrefs.php b/src/Entity/UserImPrefs.php new file mode 100644 index 0000000000..5a9b10a8f9 --- /dev/null +++ b/src/Entity/UserImPrefs.php @@ -0,0 +1,63 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user IM preferences + * + * @category DB + * @package GNUsocial + * + * @author Craig Andrews + * @copyright 2009 StatusNet Inc. + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class UserImPrefs +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'user_im_prefs', + 'fields' => [ + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'user'], + 'screenname' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'screenname on this service'], + 'transport' => ['type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'transport (ex xmpp, aim)'], + 'notify' => ['type' => 'bool', 'not null' => true, 'default' => false, 'description' => 'Notify when a new notice is sent'], + 'replies' => ['type' => 'bool', 'not null' => true, 'default' => false, 'description' => 'Send replies from people not subscribed to'], + 'updatefrompresence' => ['type' => 'bool', 'not null' => true, 'default' => false, 'description' => 'Update from presence.'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['user_id', 'transport'], + 'unique keys' => [ + 'transport_screenname_key' => ['transport', 'screenname'], + ], + 'foreign keys' => [ + 'user_im_prefs_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/UserLocationPrefs.php b/src/Entity/UserLocationPrefs.php new file mode 100644 index 0000000000..0bfac53788 --- /dev/null +++ b/src/Entity/UserLocationPrefs.php @@ -0,0 +1,56 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user location preferences + * + * @category DB + * @package GNUsocial + * + * @author Evan Prodromou + * @copyright 2009 StatusNet Inc. + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class UserLocationPrefs +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'user_location_prefs', + 'fields' => [ + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'user who has the preference'], + 'share_location' => ['type' => 'bool', 'default' => true, 'description' => 'Whether to share location data'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['user_id'], + 'foreign keys' => [ + 'user_location_prefs_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/UserUrlshortenerPrefs.php b/src/Entity/UserUrlshortenerPrefs.php new file mode 100644 index 0000000000..01cb6ae8eb --- /dev/null +++ b/src/Entity/UserUrlshortenerPrefs.php @@ -0,0 +1,60 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for user's url shortener preferences + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class UserUrlshortenerPrefs +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'user_urlshortener_prefs', + 'fields' => [ + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'user'], + 'urlshorteningservice' => ['type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'], + 'maxurllength' => ['type' => 'int', 'not null' => true, 'description' => 'urls greater than this length will be shortened, 0 = always, null = never'], + 'maxnoticelength' => ['type' => 'int', 'not null' => true, 'description' => 'notices with content greater than this value will have all urls shortened, 0 = always, -1 = only if notice text is longer than max allowed'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['user_id'], + 'foreign keys' => [ + 'user_urlshortener_prefs_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/UserUsername.php b/src/Entity/UserUsername.php new file mode 100644 index 0000000000..09b387a283 --- /dev/null +++ b/src/Entity/UserUsername.php @@ -0,0 +1,62 @@ +. + }}} */ + +namespace App\Entity; + +/** + * Entity for association between user and username + * + * @category DB + * @package GNUsocial + * + * @author Zach Copley + * @copyright 2010 StatusNet Inc. + * @author Mikael Nordfeldth + * @copyright 2009-2014 Free Software Foundation, Inc http://www.fsf.org + * @author Hugo Sales + * @copyright 2020 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ +class UserUsername +{ + // AUTOCODE BEGIN + + // AUTOCODE END + + public static function schemaDef(): array + { + return [ + 'name' => 'user_username', + 'fields' => [ + 'provider_name' => ['type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'provider name'], + 'username' => ['type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'username'], + 'user_id' => ['type' => 'int', 'not null' => true, 'description' => 'notice id this title relates to'], + 'created' => ['type' => 'datetime', 'not null' => true, 'default' => '0000-00-00 00:00:00', 'description' => 'date this record was created'], + 'modified' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], + ], + 'primary key' => ['provider_name', 'username'], + 'indexes' => [ + 'user_id_idx' => ['user_id'], + ], + 'foreign keys' => [ + 'user_username_user_id_fkey' => ['user', ['user_id' => 'id']], + ], + ]; + } +} \ No newline at end of file diff --git a/src/Entity/User_group.php b/src/Entity/User_group.php deleted file mode 100644 index 7e86140d77..0000000000 --- a/src/Entity/User_group.php +++ /dev/null @@ -1,887 +0,0 @@ -. - -defined('GNUSOCIAL') || die(); - -/** - * Table Definition for user_group - */ -class User_group extends Managed_DataObject -{ - const JOIN_POLICY_OPEN = 0; - const JOIN_POLICY_MODERATE = 1; - const CACHE_WINDOW = 201; - - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'user_group'; // table name - public $id; // int(4) primary_key not_null - public $profile_id; // int(4) primary_key not_null - public $nickname; // varchar(64) - public $fullname; // varchar(191) not 255 because utf8mb4 takes more space - public $homepage; // varchar(191) not 255 because utf8mb4 takes more space - public $description; // text - public $location; // varchar(191) not 255 because utf8mb4 takes more space - public $original_logo; // varchar(191) not 255 because utf8mb4 takes more space - public $homepage_logo; // varchar(191) not 255 because utf8mb4 takes more space - public $stream_logo; // varchar(191) not 255 because utf8mb4 takes more space - public $mini_logo; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space - public $mainpage; // varchar(191) not 255 because utf8mb4 takes more space - public $join_policy; // tinyint - public $force_scope; // tinyint - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public function getObjectType() - { - return ActivityObject::GROUP; - } - - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), - 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), - - 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'nickname for addressing'), - 'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name'), - 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'), - 'description' => array('type' => 'text', 'description' => 'group description'), - 'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'related physical location, if any'), - - 'original_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'original size logo'), - 'homepage_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'homepage (profile) size logo'), - 'stream_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'stream-sized logo'), - 'mini_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'mini logo'), - - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'), - 'mainpage' => array('type' => 'varchar', 'length' => 191, 'description' => 'page for group info to link to'), - 'join_policy' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=open; 1=requires admin approval'), - 'force_scope' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=never,1=sometimes,-1=always'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'user_group_uri_key' => array('uri'), -// when it's safe and everyone's run upgrade.php 'user_profile_id_key' => array('profile_id'), - ), - 'foreign keys' => array( - 'user_group_profile_id_fkey' => array('profile', array('profile_id' => 'id')), - ), - 'indexes' => array( - 'user_group_nickname_idx' => array('nickname'), - 'user_group_created_id_idx' => array('created', 'id'), - 'user_group_profile_id_idx' => array('profile_id'), //make this unique in future - ), - ); - } - - protected $_profile = array(); - - /** - * @return Profile - * - * @throws GroupNoProfileException if user has no profile - */ - public function getProfile() - { - if (!isset($this->_profile[$this->profile_id])) { - $profile = Profile::getKV('id', $this->profile_id); - if (!$profile instanceof Profile) { - throw new GroupNoProfileException($this); - } - $this->_profile[$this->profile_id] = $profile; - } - return $this->_profile[$this->profile_id]; - } - - public function getNickname() - { - return $this->getProfile()->getNickname(); - } - - public function getFullname() - { - return $this->getProfile()->getFullname(); - } - - public static function defaultLogo($size) - { - static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile', - AVATAR_STREAM_SIZE => 'stream', - AVATAR_MINI_SIZE => 'mini'); - return Theme::path('default-avatar-'.$sizenames[$size].'.png'); - } - - public function homeUrl() - { - return $this->getProfile()->getUrl(); - } - - public function getUri() - { - $uri = null; - if (Event::handle('StartUserGroupGetUri', array($this, &$uri))) { - if (!empty($this->uri)) { - $uri = $this->uri; - } elseif ($this->isLocal()) { - $uri = common_local_url('groupbyid', ['id' => $this->id]); - } - } - Event::handle('EndUserGroupGetUri', array($this, &$uri)); - return $uri; - } - - public function permalink() - { - $url = null; - if (Event::handle('StartUserGroupPermalink', array($this, &$url))) { - if ($this->isLocal()) { - $url = common_local_url('groupbyid', ['id' => $this->id]); - } - } - Event::handle('EndUserGroupPermalink', array($this, &$url)); - return $url; - } - - public function getNotices($offset, $limit, $since_id = null, $max_id = null) - { - // FIXME: Get the Profile::current() some other way, to avoid - // possible confusion between current session and queue process. - $stream = new GroupNoticeStream($this, Profile::current()); - - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - public function getMembers($offset = 0, $limit = null) - { - $ids = null; - if (is_null($limit) || $offset + $limit > User_group::CACHE_WINDOW) { - $ids = $this->getMemberIDs($offset, $limit); - } else { - $key = sprintf('group:member_ids:%d', $this->id); - $window = self::cacheGet($key); - if ($window === false) { - $window = $this->getMemberIDs(0, User_group::CACHE_WINDOW); - self::cacheSet($key, $window); - } - - $ids = array_slice($window, $offset, $limit); - } - - return Profile::multiGet('id', $ids); - } - - public function getMemberIDs($offset = 0, $limit = null) - { - $gm = new Group_member(); - - $gm->selectAdd(); - $gm->selectAdd('profile_id'); - - $gm->group_id = $this->id; - - $gm->orderBy('created DESC, profile_id DESC'); - - if (!is_null($limit)) { - $gm->limit($offset, $limit); - } - - $ids = array(); - - if ($gm->find()) { - while ($gm->fetch()) { - $ids[] = $gm->profile_id; - } - } - - return $ids; - } - - /** - * Get pending members, who have not yet been approved. - * - * @param int $offset - * @param int $limit - * @return Profile - */ - public function getRequests($offset = 0, $limit = null) - { - $rq = new Group_join_queue(); - $rq->group_id = $this->id; - - $members = new Profile(); - - $members->joinAdd(['id', $rq, 'profile_id']); - - if ($limit != null) { - $members->limit($offset, $limit); - } - - $members->find(); - - return $members; - } - - public function getAdminCount() - { - $block = new Group_member(); - $block->group_id = $this->id; - $block->is_admin = true; - - return $block->count(); - } - - public function getMemberCount() - { - $key = sprintf("group:member_count:%d", $this->id); - - $cnt = self::cacheGet($key); - - if (is_integer($cnt)) { - return (int) $cnt; - } - - $mem = new Group_member(); - $mem->group_id = $this->id; - - // XXX: why 'distinct'? - - $cnt = (int) $mem->count('distinct profile_id'); - - self::cacheSet($key, $cnt); - - return $cnt; - } - - public function getBlockedCount() - { - // XXX: WORM cache this - - $block = new Group_block(); - $block->group_id = $this->id; - - return $block->count(); - } - - public function getQueueCount() - { - // XXX: WORM cache this - - $queue = new Group_join_queue(); - $queue->group_id = $this->id; - - return $queue->count(); - } - - // offset is null because DataObject wants it, 0 would mean no results - public function getAdmins($offset = null, $limit = null) - { - $admins = new Profile(); - $admins->joinAdd(['id', 'group_member:profile_id']); - $admins->whereAdd(sprintf( - 'group_member.group_id = %d AND group_member.is_admin IS TRUE', - $this->getID() - )); - $admins->orderBy('group_member.modified, group_member.profile_id'); - $admins->limit($offset, $limit); - $admins->find(); - - return $admins; - } - - // offset is null because DataObject wants it, 0 would mean no results - public function getBlocked($offset = null, $limit = null) - { - $blocked = new Profile(); - $blocked->joinAdd(array('id', 'group_block:blocked')); - $blocked->whereAdd(sprintf('group_block.group_id = %u', $this->id)); - $blocked->orderBy('group_block.modified DESC, group_block.blocked DESC'); - $blocked->limit($offset, $limit); - $blocked->find(); - - return $blocked; - } - - public function setOriginal($filename) - { - // This should be handled by the Profile->setOriginal function so user and group avatars are handled the same - $imagefile = new ImageFile(null, Avatar::path($filename)); - - $sizes = array('homepage_logo' => AVATAR_PROFILE_SIZE, - 'stream_logo' => AVATAR_STREAM_SIZE, - 'mini_logo' => AVATAR_MINI_SIZE); - - $orig = clone($this); - $this->original_logo = Avatar::url($filename); - foreach ($sizes as $name=>$size) { - $filename = Avatar::filename( - $this->profile_id, - image_type_to_extension($imagefile->preferredType()), - $size, - common_timestamp() - ); - $imagefile->resizeTo(Avatar::path($filename), array('width'=>$size, 'height'=>$size)); - $this->$name = Avatar::url($filename); - } - common_debug(common_log_objstring($this)); - return $this->update($orig); - } - - public function getBestName() - { - return ($this->fullname) ? $this->fullname : $this->nickname; - } - - /** - * Gets the full name (if filled) with nickname as a parenthetical, or the nickname alone - * if no fullname is provided. - * - * @return string - */ - public function getFancyName() - { - if ($this->fullname) { - // TRANS: Full name of a profile or group followed by nickname in parens - return sprintf(_m('FANCYNAME', '%1$s (%2$s)'), $this->fullname, $this->nickname); - } else { - return $this->nickname; - } - } - - public function getAliases() - { - $aliases = array(); - - // XXX: cache this - - $alias = new Group_alias(); - - $alias->group_id = $this->id; - - if ($alias->find()) { - while ($alias->fetch()) { - $aliases[] = $alias->alias; - } - } - - $alias->free(); - - return $aliases; - } - - public function setAliases($newaliases) - { - $newaliases = array_unique($newaliases); - - $oldaliases = $this->getAliases(); - - // Delete stuff that's old that not in new - - $to_delete = array_diff($oldaliases, $newaliases); - - // Insert stuff that's in new and not in old - - $to_insert = array_diff($newaliases, $oldaliases); - - $alias = new Group_alias(); - - $alias->group_id = $this->id; - - foreach ($to_delete as $delalias) { - $alias->alias = $delalias; - $result = $alias->delete(); - if (!$result) { - common_log_db_error($alias, 'DELETE', __FILE__); - return false; - } - } - - foreach ($to_insert as $insalias) { - if ($insalias === $this->nickname) { - continue; - } - $alias->alias = Nickname::normalize($insalias, true); - $result = $alias->insert(); - if (!$result) { - common_log_db_error($alias, 'INSERT', __FILE__); - return false; - } - } - - return true; - } - - public static function getForNickname($nickname, Profile $profile = null) - { - $nickname = Nickname::normalize($nickname); - - // Are there any matching remote groups this profile's in? - if ($profile instanceof Profile) { - $group = $profile->getGroups(0, null); - while ($group instanceof User_group && $group->fetch()) { - if ($group->nickname == $nickname) { - // @fixme is this the best way? - return clone($group); - } - } - } - - // If not, check local groups. - $group = Local_group::getKV('nickname', $nickname); - if ($group instanceof Local_group) { - return User_group::getKV('id', $group->group_id); - } - $alias = Group_alias::getKV('alias', $nickname); - if ($alias instanceof Group_alias) { - return User_group::getKV('id', $alias->group_id); - } - return null; - } - - public function getUserMembers() - { - // XXX: cache this - - $user = new User(); - - $user->query(sprintf( - 'SELECT id FROM %1$s INNER JOIN group_member ' . - 'ON %1$s.id = group_member.profile_id ' . - 'WHERE group_member.group_id = %2$d ', - $user->escapedTableName(), - $this->id - )); - - $ids = []; - - while ($user->fetch()) { - $ids[] = $user->id; - } - - $user->free(); - - return $ids; - } - - public static function maxDescription() - { - $desclimit = common_config('group', 'desclimit'); - // null => use global limit (distinct from 0!) - if (is_null($desclimit)) { - $desclimit = common_config('site', 'textlimit'); - } - return $desclimit; - } - - public static function descriptionTooLong($desc) - { - $desclimit = self::maxDescription(); - return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit)); - } - - public function asAtomEntry($namespace = false, $source = false) - { - $xs = new XMLStringer(true); - - if ($namespace) { - $attrs = array('xmlns' => 'http://www.w3.org/2005/Atom', - 'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'); - } else { - $attrs = array(); - } - - $xs->elementStart('entry', $attrs); - - if ($source) { - $xs->elementStart('source'); - $xs->element('id', null, $this->permalink()); - $xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name')); - $xs->element('link', array('href' => $this->permalink())); - $xs->element('updated', null, $this->modified); - $xs->elementEnd('source'); - } - - $xs->element('title', null, $this->nickname); - $xs->element('summary', null, common_xml_safe_str($this->description)); - - $xs->element('link', array('rel' => 'alternate', - 'href' => $this->permalink())); - - $xs->element('id', null, $this->permalink()); - - $xs->element('published', null, common_date_w3dtf($this->created)); - $xs->element('updated', null, common_date_w3dtf($this->modified)); - - $xs->element( - 'content', - array('type' => 'html'), - common_xml_safe_str($this->description) - ); - - $xs->elementEnd('entry'); - - return $xs->getString(); - } - - public function asAtomAuthor() - { - $xs = new XMLStringer(true); - - $xs->elementStart('author'); - $xs->element('name', null, $this->nickname); - $xs->element('uri', null, $this->permalink()); - $xs->elementEnd('author'); - - return $xs->getString(); - } - - /** - * Returns an XML string fragment with group information as an - * Activity Streams noun object with the given element type. - * - * Assumes that 'activity', 'georss', and 'poco' namespace has been - * previously defined. - * - * @param string $element one of 'actor', 'subject', 'object', 'target' - * - * @return string - */ - public function asActivityNoun($element) - { - $noun = ActivityObject::fromGroup($this); - return $noun->asString('activity:' . $element); - } - - public function getAvatar() - { - return empty($this->homepage_logo) - ? User_group::defaultLogo(AVATAR_PROFILE_SIZE) - : $this->homepage_logo; - } - - public static function register($fields) - { - if (!empty($fields['userid'])) { - $profile = Profile::getKV('id', $fields['userid']); - if ($profile && !$profile->hasRight(Right::CREATEGROUP)) { - common_log(LOG_WARNING, "Attempted group creation from banned user: " . $profile->nickname); - - // TRANS: Client exception thrown when a user tries to create a group while banned. - throw new ClientException(_('You are not allowed to create groups on this site.'), 403); - } - } - - $fields['nickname'] = Nickname::normalize($fields['nickname']); - - // MAGICALLY put fields into current scope - // @fixme kill extract(); it makes debugging absurdly hard - - $defaults = [ - 'nickname' => null, - 'fullname' => null, - 'homepage' => null, - 'description' => null, - 'location' => null, - 'uri' => null, - 'mainpage' => null, - 'aliases' => [], - 'userid' => null, - ]; - - $fields = array_merge($defaults, $fields); - - extract($fields); - - $group = new User_group(); - - if (empty($uri)) { - // fill in later... - $uri = null; - } - if (empty($mainpage)) { - $mainpage = common_local_url('showgroup', array('nickname' => $nickname)); - } - - // We must create a new, incrementally assigned profile_id - $profile = new Profile(); - $profile->nickname = $nickname; - $profile->fullname = $fullname; - $profile->profileurl = $mainpage; - $profile->homepage = $homepage; - $profile->bio = $description; - $profile->location = $location; - $profile->created = common_sql_now(); - - $group->nickname = $profile->nickname; - $group->fullname = $profile->fullname; - $group->homepage = $profile->homepage; - $group->description = $profile->bio; - $group->location = $profile->location; - $group->mainpage = $profile->profileurl; - $group->created = $profile->created; - - $profile->query('START TRANSACTION'); - $id = $profile->insert(); - if ($id === false) { - $profile->query('ROLLBACK'); - throw new ServerException(_('Profile insertion failed')); - } - - $group->profile_id = $id; - $group->uri = $uri; - - if (isset($fields['join_policy'])) { - $group->join_policy = intval($fields['join_policy']); - } else { - $group->join_policy = 0; - } - - if (isset($fields['force_scope'])) { - $group->force_scope = intval($fields['force_scope']); - } else { - $group->force_scope = 0; - } - - if (Event::handle('StartGroupSave', array(&$group))) { - $result = $group->insert(); - - if ($result === false) { - common_log_db_error($group, 'INSERT', __FILE__); - // TRANS: Server exception thrown when creating a group failed. - throw new ServerException(_('Could not create group.')); - } - - if (!isset($uri) || empty($uri)) { - $orig = clone($group); - $group->uri = common_local_url('groupbyid', array('id' => $group->id)); - $result = $group->update($orig); - if (!$result) { - common_log_db_error($group, 'UPDATE', __FILE__); - // TRANS: Server exception thrown when updating a group URI failed. - throw new ServerException(_('Could not set group URI.')); - } - } - - $result = $group->setAliases($aliases); - - if (!$result) { - // TRANS: Server exception thrown when creating group aliases failed. - throw new ServerException(_('Could not create aliases.')); - } - - $member = new Group_member(); - - $member->group_id = $group->id; - $member->profile_id = $userid; - $member->is_admin = true; - $member->created = $group->created; - - $result = $member->insert(); - - if (!$result) { - common_log_db_error($member, 'INSERT', __FILE__); - // TRANS: Server exception thrown when setting group membership failed. - throw new ServerException(_('Could not set group membership.')); - } - - self::blow('profile:groups:%d', $userid); - - if ($local) { - $local_group = new Local_group(); - - $local_group->group_id = $group->id; - $local_group->nickname = $nickname; - $local_group->created = common_sql_now(); - - $result = $local_group->insert(); - - if (!$result) { - common_log_db_error($local_group, 'INSERT', __FILE__); - // TRANS: Server exception thrown when saving local group information failed. - throw new ServerException(_('Could not save local group info.')); - } - } - - Event::handle('EndGroupSave', array($group)); - } - - $profile->query('COMMIT'); - - return $group; - } - - /** - * Handle cascading deletion, on the model of notice and profile. - * - * This should handle freeing up cached entries for the group's - * id, nickname, URI, and aliases. There may be other areas that - * are not de-cached in the UI, including the sidebar lists on - * GroupsAction - */ - public function delete($useWhere = false) - { - if (empty($this->id)) { - common_log(LOG_WARNING, "Ambiguous User_group->delete(); skipping related tables."); - return parent::delete($useWhere); - } - - // Safe to delete in bulk for now - - $related = array('Group_inbox', - 'Group_block', - 'Group_member', - 'Related_group'); - - Event::handle('UserGroupDeleteRelated', array($this, &$related)); - - foreach ($related as $cls) { - $inst = new $cls(); - $inst->group_id = $this->id; - - if ($inst->find()) { - while ($inst->fetch()) { - $dup = clone($inst); - $dup->delete(); - } - } - } - - // And related groups in the other direction... - $inst = new Related_group(); - $inst->related_group_id = $this->id; - $inst->delete(); - - // Aliases and the local_group entry need to be cleared explicitly - // or we'll miss clearing some cache keys; that can make it hard - // to create a new group with one of those names or aliases. - $this->setAliases(array()); - - // $this->isLocal() but we're using the resulting object - $local = Local_group::getKV('group_id', $this->id); - if ($local instanceof Local_group) { - $local->delete(); - } - - $result = parent::delete($useWhere); - - try { - $profile = $this->getProfile(); - $profile->delete(); - } catch (GroupNoProfileException $unp) { - common_log( - LOG_INFO, - "Group {$this->nickname} has no profile; continuing deletion." - ); - } - - // blow the cached ids - self::blow('user_group:notice_ids:%d', $this->id); - - return $result; - } - - public function update($dataObject=false) - { - // Whenever the User_group is updated, find the Local_group - // and update its nickname too. - if ($this->nickname != $dataObject->nickname) { - $local = Local_group::getKV('group_id', $this->id); - if ($local instanceof Local_group) { - common_debug("Updating Local_group ({$this->id}) nickname from {$dataObject->nickname} to {$this->nickname}"); - $local->setNickname($this->nickname); - } - } - - // Also make sure the Profile table is up to date! - $fields = array(/*group field => profile field*/ - 'nickname' => 'nickname', - 'fullname' => 'fullname', - 'mainpage' => 'profileurl', - 'homepage' => 'homepage', - 'description' => 'bio', - 'location' => 'location', - 'created' => 'created', - 'modified' => 'modified', - ); - $profile = $this->getProfile(); - $origpro = clone($profile); - foreach ($fields as $gf=>$pf) { - $profile->$pf = $this->$gf; - } - if ($profile->update($origpro) === false) { - throw new ServerException(_('Unable to update profile')); - } - - return parent::update($dataObject); - } - - public function isPrivate() - { - return ($this->join_policy == self::JOIN_POLICY_MODERATE && - intval($this->force_scope) === 1); - } - - public function isLocal() - { - $local = Local_group::getKV('group_id', $this->id); - return ($local instanceof Local_group); - } - - public static function groupsFromText($text, Profile $profile) - { - $groups = array(); - - /* extract all !group */ - $count = preg_match_all( - '/(?:^|\s)!(' . Nickname::DISPLAY_FMT . ')/', - strtolower($text), - $match - ); - - if (!$count) { - return $groups; - } - - foreach (array_unique($match[1]) as $nickname) { - $group = self::getForNickname($nickname, $profile); - if ($group instanceof User_group && $profile->isMember($group)) { - $groups[] = clone($group); - } - } - - return $groups; - } - - public static function idsFromText($text, Profile $profile) - { - $ids = array(); - $groups = self::groupsFromText($text, $profile); - foreach ($groups as $group) { - $ids[$group->id] = true; - } - return array_keys($ids); - } -} diff --git a/src/Entity/User_im_prefs.php b/src/Entity/User_im_prefs.php deleted file mode 100644 index 0e465ad2c4..0000000000 --- a/src/Entity/User_im_prefs.php +++ /dev/null @@ -1,69 +0,0 @@ -. - -/** - * Data class for user IM preferences - * - * @category Data - * @package GNUsocial - * @author Craig Andrews - * @copyright 2009 StatusNet Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class User_im_prefs extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'user_im_prefs'; // table name - public $user_id; // int(4) primary_key not_null - public $screenname; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $transport; // varchar(191) not_null not 255 because utf8mb4 takes more space - public $notify; // bool not_null default_false - public $replies; // bool not_null default_false - public $updatefrompresence; // bool not_null default_false - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'), - 'screenname' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'screenname on this service'), - 'transport' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'transport (ex xmpp, aim)'), - 'notify' => array('type' => 'bool', 'not null' => true, 'default' => false, 'description' => 'Notify when a new notice is sent'), - 'replies' => array('type' => 'bool', 'not null' => true, 'default' => false, 'description' => 'Send replies from people not subscribed to'), - 'updatefrompresence' => array('type' => 'bool', 'not null' => true, 'default' => false, 'description' => 'Update from presence.'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('user_id', 'transport'), - 'unique keys' => array( - 'user_im_prefs_transport_screenname_key' => array('transport', 'screenname'), - ), - 'foreign keys' => array( - 'user_im_prefs_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - ); - } -} diff --git a/src/Entity/User_location_prefs.php b/src/Entity/User_location_prefs.php deleted file mode 100644 index 74e09fa0ec..0000000000 --- a/src/Entity/User_location_prefs.php +++ /dev/null @@ -1,58 +0,0 @@ -. - -/** - * Data class for user location preferences - * - * @category Data - * @package GNUsocial - * @author Evan Prodromou - * @copyright 2009 StatusNet Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class User_location_prefs extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'user_location_prefs'; // table name - public $user_id; // int(4) primary_key not_null - public $share_location; // bool default_true - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who has the preference'), - 'share_location' => array('type' => 'bool', 'default' => true, 'description' => 'Whether to share location data'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('user_id'), - 'foreign keys' => array( - 'user_location_prefs_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - ); - } -} diff --git a/src/Entity/User_urlshortener_prefs.php b/src/Entity/User_urlshortener_prefs.php deleted file mode 100644 index 2bf2dad76f..0000000000 --- a/src/Entity/User_urlshortener_prefs.php +++ /dev/null @@ -1,120 +0,0 @@ -. - -/* - * @copyright 2010 StatusNet, Inc. - * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later - */ - -defined('GNUSOCIAL') || die(); - -class User_urlshortener_prefs extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'user_urlshortener_prefs'; // table name - public $user_id; // int(4) primary_key not_null - public $urlshorteningservice; // varchar(50) default_ur1.ca - public $maxurllength; // int(4) not_null - public $maxnoticelength; // int(4) not_null - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'), - 'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'), - 'maxurllength' => array('type' => 'int', 'not null' => true, 'description' => 'urls greater than this length will be shortened, 0 = always, null = never'), - 'maxnoticelength' => array('type' => 'int', 'not null' => true, 'description' => 'notices with content greater than this value will have all urls shortened, 0 = always, -1 = only if notice text is longer than max allowed'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('user_id'), - 'foreign keys' => array( - 'user_urlshortener_prefs_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - ); - } - - public static function maxUrlLength($user) - { - $def = common_config('url', 'maxurllength'); - - $prefs = self::getPrefs($user); - - if (empty($prefs)) { - return $def; - } else { - return $prefs->maxurllength; - } - } - - public static function maxNoticeLength($user) - { - $def = common_config('url', 'maxnoticelength'); - - if ($def == -1) { - /* - * maxContent==0 means infinite length, - * but maxNoticeLength==0 means "always shorten" - * so if maxContent==0 we must set this to -1 - */ - $def = Notice::maxContent() ?: -1; - } - - $prefs = self::getPrefs($user); - - if (empty($prefs)) { - return $def; - } else { - return $prefs->maxnoticelength; - } - } - - public static function urlShorteningService($user) - { - $def = common_config('url', 'shortener'); - - $prefs = self::getPrefs($user); - - if (empty($prefs)) { - if (!empty($user)) { - return $user->urlshorteningservice; - } else { - return $def; - } - } else { - return $prefs->urlshorteningservice; - } - } - - public static function getPrefs($user) - { - if (empty($user)) { - return null; - } - - $prefs = User_urlshortener_prefs::getKV('user_id', $user->id); - - return $prefs; - } -} diff --git a/src/Entity/User_username.php b/src/Entity/User_username.php deleted file mode 100644 index 8007e8b90a..0000000000 --- a/src/Entity/User_username.php +++ /dev/null @@ -1,79 +0,0 @@ -. - -/** - * Table Definition for user_username - */ - -defined('GNUSOCIAL') || die(); - -class User_username extends Managed_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'user_username'; // table name - public $user_id; // int(4) not_null - public $provider_name; // varchar(191) primary_key not_null not 255 because utf8mb4 takes more space - public $username; // varchar(191) primary_key not_null not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - public static function schemaDef() - { - return array( - 'fields' => array( - 'provider_name' => array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'provider name'), - 'username' => array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'username'), - 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice id this title relates to'), - 'created' => array('type' => 'datetime', 'description' => 'date this record was created'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('provider_name', 'username'), - 'foreign keys' => array( - 'user_username_user_id_fkey' => array('user', array('user_id' => 'id')), - ), - 'indexes' => array( - 'user_username_user_id_idx' => array('user_id'), - ), - ); - } - - /** - * Register a user with a username on a given provider - * @param User User object - * @param string username on the given provider - * @param provider_name string name of the provider - * @return mixed User_username instance if the registration succeeded, false if it did not - */ - public static function register($user, $username, $provider_name) - { - $user_username = new User_username(); - $user_username->user_id = $user->id; - $user_username->provider_name = $provider_name; - $user_username->username = $username; - $user_username->created = common_sql_now(); - - if ($user_username->insert()) { - return $user_username; - } else { - return false; - } - } -} diff --git a/src/Entity/status_network.ini b/src/Entity/status_network.ini deleted file mode 100644 index b298daae46..0000000000 --- a/src/Entity/status_network.ini +++ /dev/null @@ -1,30 +0,0 @@ -[status_network] -site_id = 129 -nickname = 130 -hostname = 2 -pathname = 2 -dbhost = 2 -dbuser = 2 -dbpass = 2 -dbname = 2 -sitename = 2 -theme = 2 -logo = 2 -created = 142 -modified = 384 - -[status_network__keys] -site_id = K -nickname = U -hostname = U -pathname = U - -[status_network_tag] -site_id = 129 -tag = 130 -created = 142 - -[status_network_tag__keys] -site_id = K -tag = K -