diff --git a/src/Entity/Attention.php b/src/Entity/Attention.php index 88e314f117..c1c09c5608 100644 --- a/src/Entity/Attention.php +++ b/src/Entity/Attention.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Attention { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Avatar.php b/src/Entity/Avatar.php index 87e0f3cfcb..27d9353f0d 100644 --- a/src/Entity/Avatar.php +++ b/src/Entity/Avatar.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Avatar { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Config.php b/src/Entity/Config.php index 4b24053228..370f2e5400 100644 --- a/src/Entity/Config.php +++ b/src/Entity/Config.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Config { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ConfirmAddress.php b/src/Entity/ConfirmAddress.php index 198c6a2f45..f185c08773 100644 --- a/src/Entity/ConfirmAddress.php +++ b/src/Entity/ConfirmAddress.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ConfirmAddress { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Consumer.php b/src/Entity/Consumer.php index 2b34cf3675..0a6b8f160f 100644 --- a/src/Entity/Consumer.php +++ b/src/Entity/Consumer.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Consumer { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Conversation.php b/src/Entity/Conversation.php index ace4a5350c..ca479fbb0d 100644 --- a/src/Entity/Conversation.php +++ b/src/Entity/Conversation.php @@ -33,9 +33,9 @@ namespace App\Entity; */ class Conversation { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/File.php b/src/Entity/File.php index 740e40c1d3..732591e0cb 100644 --- a/src/Entity/File.php +++ b/src/Entity/File.php @@ -35,863 +35,9 @@ namespace App\Entity; */ class File { -<<<<<<< HEAD - public $__table = 'file'; // table name - public $id; // int(4) primary_key not_null - public $urlhash; // varchar(64) unique_key - public $url; // text - public $filehash; // varchar(64) indexed - public $mimetype; // varchar(50) - public $size; // int(4) - public $title; // text() - public $date; // int(4) - public $protected; // int(4) - public $filename; // text() - public $width; // int(4) - public $height; // int(4) - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + // {{{ Autocode - const URLHASH_ALG = 'sha256'; - const FILEHASH_ALG = 'sha256'; - - public static function schemaDef() - { - return array( - 'fields' => array( - 'id' => array('type' => 'serial', 'not null' => true), - 'urlhash' => array('type' => 'varchar', 'length' => 64, 'description' => 'sha256 of destination URL (url field)'), - 'url' => array('type' => 'text', 'description' => 'destination URL after following possible redirections'), - 'filehash' => array('type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'), - 'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'), - 'size' => array('type' => 'int', 'description' => 'size of resource when available'), - 'title' => array('type' => 'text', 'description' => 'title of resource when available'), - 'date' => array('type' => 'int', 'description' => 'date of resource according to http query'), - 'protected' => array('type' => 'int', 'description' => 'true when URL is private (needs login)'), - 'filename' => array('type' => 'text', 'description' => 'if file is stored locally (too) this is the filename'), - 'width' => array('type' => 'int', 'description' => 'width in pixels, if it can be described as such and data is available'), - 'height' => array('type' => 'int', 'description' => 'height in pixels, if it can be described as such and data is available'), - 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'file_urlhash_key' => array('urlhash'), - ), - 'indexes' => array( - 'file_filehash_idx' => array('filehash'), - ), - ); - } - - public static function isProtected($url) - { - $protected_urls_exps = array( - 'https://www.facebook.com/login.php', - common_path('main/login') - ); - - foreach ($protected_urls_exps as $protected_url_exp) { - if (preg_match('!^'.preg_quote($protected_url_exp).'(.*)$!i', $url) === 1) { - return true; - } - } - - return false; - } - - /** - * Save a new file record. - * - * @param array $redir_data lookup data eg from File_redirection::where() - * @param string $given_url - * @return File - * @throws ServerException - */ - public static function saveNew(array $redir_data, $given_url) - { - $file = null; - try { - // I don't know why we have to keep doing this but we run a last check to avoid - // uniqueness bugs. - $file = File::getByUrl($given_url); - return $file; - } catch (NoResultException $e) { - // We don't have the file's URL since before, so let's continue. - } - - // If the given url is a local attachment url, don't save a new file record. - $uh = parse_url($given_url, PHP_URL_HOST); - $up = parse_url($given_url, PHP_URL_PATH); - if ($uh == common_config('site', 'server') || $uh == common_config('attachments', 'server')) { - unset($uh); - $r = Router::get(); - // Skip the / in the beginning or $r->map won't match - try { - $args = $r->map(mb_substr($up, 1)); - if ($args['action'] === 'attachment' || - $args['action'] === 'attachment_view' || - $args['action'] === 'attachment_download' || - $args['action'] === 'attachment_thumbnail' ) { - try { - if (array_key_exists('attachment', $args)) { - return File::getByID((int)$args['attachment']); - } elseif (array_key_exists('filehash', $args)) { - $file = File::getByHash($args['filehash']); - $file->fetch(); - return $file; - } - } catch (NoResultException $e) { - // apparently this link goes to us, but is _not_ an existing attachment (File) ID? - } - } - } catch (Exception $e) { - // Some other exception was thrown from $r->map, likely a - // ClientException (404) because of some malformed link to - // our own instance. It's still a valid URL however, so we - // won't abort anything... I noticed this when linking: - // https://social.umeahackerspace.se/mmn/foaf' (notice the - // apostrophe in the end, making it unrecognizable for our - // URL routing. - // That specific issue (the apostrophe being part of a link - // is something that may or may not have been fixed since, - // in lib/util.php in common_replace_urls_callback(). - } - } - - $file = new File; - $file->url = $given_url; - if (!empty($redir_data['protected'])) { - $file->protected = $redir_data['protected']; - } - if (!empty($redir_data['title'])) { - $file->title = $redir_data['title']; - } - if (!empty($redir_data['type'])) { - $file->mimetype = $redir_data['type']; - } - if (!empty($redir_data['size'])) { - $file->size = (int)$redir_data['size']; - } - if (isset($redir_data['time']) && $redir_data['time'] > 0) { - $file->date = (int)$redir_data['time']; - } - $file->saveFile(); - return $file; - } - - public function saveFile() - { - $this->urlhash = is_null($this->url) ? null : self::hashurl($this->url); - - if (!Event::handle('StartFileSaveNew', array(&$this))) { - throw new ServerException('File not saved due to an aborted StartFileSaveNew event.'); - } - - $this->id = $this->insert(); - - if ($this->id === false) { - throw new ServerException('File/URL metadata could not be saved to the database.'); - } - - Event::handle('EndFileSaveNew', array($this)); - } - - /** - * Go look at a URL and possibly save data about it if it's new: - * - follow redirect chains and store them in file_redirection - * - if a thumbnail is available, save it in file_thumbnail - * - save file record with basic info - * - optionally save a file_to_post record - * - return the File object with the full reference - * - * @param string $given_url the URL we're looking at - * @param Notice|null $notice (optional) - * @param bool $followRedirects defaults to true - * - * @return mixed File on success, -1 on some errors - * - * @throws ServerException on failure - */ - public static function processNew($given_url, ?Notice $notice=null, bool $followRedirects=true) - { - if (empty($given_url)) { - throw new ServerException('No given URL to process'); - } - - $given_url = File_redirection::_canonUrl($given_url); - if (empty($given_url)) { - throw new ServerException('No canonical URL from given URL to process'); - } - - $redir = File_redirection::where($given_url); - try { - $file = $redir->getFile(); - } catch (EmptyPkeyValueException $e) { - common_log(LOG_ERR, 'File_redirection::where gave object with empty file_id for given_url '._ve($given_url)); - throw new ServerException('URL processing failed without new File object'); - } catch (NoResultException $e) { - // This should not happen - common_log(LOG_ERR, 'File_redirection after discovery could still not return a File object.'); - throw new ServerException('URL processing failed without new File object'); - } - - if ($notice instanceof Notice) { - File_to_post::processNew($file, $notice); - } - - return $file; - } - - public static function respectsQuota(Profile $scoped, $fileSize) - { - if ($fileSize > common_config('attachments', 'file_quota')) { - // TRANS: Message used to be inserted as %2$s in the text "No file may - // TRANS: be larger than %1$d byte and the file you sent was %2$s.". - // TRANS: %1$d is the number of bytes of an uploaded file. - $fileSizeText = sprintf(_m('%1$d byte', '%1$d bytes', $fileSize), $fileSize); - - $fileQuota = common_config('attachments', 'file_quota'); - // TRANS: Message given if an upload is larger than the configured maximum. - // TRANS: %1$d (used for plural) is the byte limit for uploads, - // TRANS: %2$s is the proper form of "n bytes". This is the only ways to have - // TRANS: gettext support multiple plurals in the same message, unfortunately... - throw new ClientException( - sprintf( - _m( - 'No file may be larger than %1$d byte and the file you sent was %2$s. Try to upload a smaller version.', - 'No file may be larger than %1$d bytes and the file you sent was %2$s. Try to upload a smaller version.', - $fileQuota - ), - $fileQuota, - $fileSizeText - ) - ); - } - - $file = new File; - - $query = "SELECT sum(size) AS total - FROM file - INNER JOIN file_to_post - ON file_to_post.file_id = file.id - INNER JOIN notice - ON file_to_post.post_id = notice.id - WHERE profile_id = {$scoped->id} AND - filename IS NULL AND - file.url IS NOT NULL"; - $file->query($query); - $file->fetch(); - $total = $file->total + $fileSize; - if ($total > common_config('attachments', 'user_quota')) { - // TRANS: Message given if an upload would exceed user quota. - // TRANS: %d (number) is the user quota in bytes and is used for plural. - throw new ClientException( - sprintf( - _m( - 'A file this large would exceed your user quota of %d byte.', - 'A file this large would exceed your user quota of %d bytes.', - common_config('attachments', 'user_quota') - ), - common_config('attachments', 'user_quota') - ) - ); - } - $query .= ' AND EXTRACT(MONTH FROM file.modified) = EXTRACT(MONTH FROM CURRENT_DATE)' - . ' AND EXTRACT(YEAR FROM file.modified) = EXTRACT(YEAR FROM CURRENT_DATE)'; - $file->query($query); - $file->fetch(); - $total = $file->total + $fileSize; - if ($total > common_config('attachments', 'monthly_quota')) { - // TRANS: Message given id an upload would exceed a user's monthly quota. - // TRANS: $d (number) is the monthly user quota in bytes and is used for plural. - throw new ClientException( - sprintf( - _m( - 'A file this large would exceed your monthly quota of %d byte.', - 'A file this large would exceed your monthly quota of %d bytes.', - common_config('attachments', 'monthly_quota') - ), - common_config('attachments', 'monthly_quota') - ) - ); - } - return true; - } - - public function getFilename() - { - return self::tryFilename($this->filename); - } - - public function getSize(): int - { - return (int)$this->size; - } - - // where should the file go? - - public static function filename(Profile $profile, $origname, $mimetype) - { - $ext = self::guessMimeExtension($mimetype, $origname); - - // Normalize and make the original filename more URL friendly. - $origname = basename($origname, ".$ext"); - if (class_exists('Normalizer')) { - // http://php.net/manual/en/class.normalizer.php - // http://www.unicode.org/reports/tr15/ - $origname = Normalizer::normalize($origname, Normalizer::FORM_KC); - } - $origname = preg_replace('/[^A-Za-z0-9\.\_]/', '_', $origname); - - $nickname = $profile->getNickname(); - $datestamp = strftime('%Y%m%d', time()); - do { - // generate new random strings until we don't run into a filename collision. - $random = strtolower(common_confirmation_code(16)); - $filename = "$nickname-$datestamp-$origname-$random.$ext"; - } while (file_exists(self::path($filename))); - return $filename; - } - - /** - * @param string $filename - * @return null|string|bool Value from the 'extblacklist' array, in the config - * @throws ServerException - */ - public static function getSafeExtension(string $filename) - { - if (preg_match('/^.+?\.([A-Za-z0-9]+)$/', $filename, $matches) === 1) { - // we matched on a file extension, so let's see if it means something. - $ext = mb_strtolower($matches[1]); - $blacklist = common_config('attachments', 'extblacklist'); - // If we got an extension from $filename we want to check if it's in a blacklist - // so we avoid people uploading restricted files - if (array_key_exists($ext, $blacklist)) { - if (!is_string($blacklist[$ext])) { - // Blocked - return false; - } - // return a safe replacement extension ('php' => 'phps' for example) - return $blacklist[$ext]; - } else { - // the attachment extension based on its filename was not blacklisted so it's ok to use it - return $ext; - } - } else { - // No extension - return null; - } - } - - /** - * @param $mimetype string The mimetype we've discovered for this file. - * @param $filename string An optional filename which we can use on failure. - * @return mixed|string - * @throws ClientException - * @throws ServerException - */ - public static function guessMimeExtension($mimetype, $filename=null) - { - try { - // first see if we know the extension for our mimetype - $ext = common_supported_mime_to_ext($mimetype); - // we do, so use it! - return $ext; - } catch (UnknownMimeExtensionException $e) { - // We don't know the extension for this mimetype, but let's guess. - // If we can't recognize the extension from the MIME, we try - // to guess based on filename, if one was supplied. - if (!is_null($filename)) { - $ext = self::getSafeExtension($filename); - if ($ext === false) { - // we don't have a safe replacement extension - throw new ClientException(_m('Blacklisted file extension.')); - } else { - return $ext; - } - } - } catch (Exception $e) { - common_log(LOG_INFO, 'Problem when figuring out extension for mimetype: '._ve($e)); - } - - // If nothing else has given us a result, try to extract it from - // the mimetype value (this turns .jpg to .jpeg for example...) - $matches = array(); - // Will get jpeg from image/jpeg as well as json from application/jrd+json - if (!preg_match('/[\/+-\.]([a-z0-9]+)/', mb_strtolower($mimetype), $matches)) { - throw new Exception("Malformed mimetype: {$mimetype}"); - } - return mb_strtolower($matches[1]); - } - - /** - * Validation for as-saved base filenames - * @param mixed $filename - * @return false|int - */ - public static function validFilename($filename) - { - return preg_match('/^[A-Za-z0-9._-]+$/', $filename); - } - - /** - * @param mixed $filename - * @return string - * @throws InvalidFilenameException - */ - public static function tryFilename($filename): string - { - if (!self::validFilename($filename)) { - throw new InvalidFilenameException($filename); - } - // if successful, return the filename for easy if-statementing - return $filename; - } - - /** - * Construct a path - * - * @param mixed $filename Will be tested by tryFilename - * @param string|null $dir Attachments directory by default - * @param bool $test_filename - * @return string - * @throws InvalidFilenameException - * @throws ServerException - */ - public static function path($filename, ?string $dir = null, bool $test_filename = true) - { - if ($test_filename) { - self::tryFilename($filename); - } - - $dir = $dir ?? common_config('attachments', 'dir'); - - if (!in_array($dir[mb_strlen($dir)-1], ['/', '\\'])) { - $dir .= DIRECTORY_SEPARATOR; - } - - return $dir . $filename; - } - - /** - * Don't use for attachments, only for assets. - * - * @param $filename - * @return mixed|string - * @throws InvalidFilenameException - * @throws ServerException - */ - public static function url($filename) - { - self::tryFilename($filename); - - if (common_config('site', 'private')) { - return common_local_url( - 'getfile', - array('filename' => $filename) - ); - } - - if (GNUsocial::useHTTPS()) { - $sslserver = common_config('attachments', 'sslserver'); - - if (empty($sslserver)) { - // XXX: this assumes that background dir == site dir + /file/ - // not true if there's another server - if (is_string(common_config('site', 'sslserver')) && - mb_strlen(common_config('site', 'sslserver')) > 0) { - $server = common_config('site', 'sslserver'); - } elseif (common_config('site', 'server')) { - $server = common_config('site', 'server'); - } - $path = common_config('site', 'path') . '/file/'; - } else { - $server = $sslserver; - $path = common_config('attachments', 'sslpath'); - if (empty($path)) { - $path = common_config('attachments', 'path'); - } - } - - $protocol = 'https'; - } else { - $path = common_config('attachments', 'path'); - $server = common_config('attachments', 'server'); - - if (empty($server)) { - $server = common_config('site', 'server'); - } - - $ssl = common_config('attachments', 'ssl'); - - $protocol = ($ssl) ? 'https' : 'http'; - } - - if ($path[strlen($path)-1] != '/') { - $path .= '/'; - } - - if ($path[0] != '/') { - $path = '/'.$path; - } - - return $protocol.'://'.$server.$path.$filename; - } - - public static $_enclosures = array(); - - public function getEnclosure() - { - if (isset(self::$_enclosures[$this->getID()])) { - return self::$_enclosures[$this->getID()]; - } - - $enclosure = (object) array(); - foreach (array('title', 'url', 'date', 'modified', 'size', 'mimetype', 'width', 'height') as $key) { - if ($this->$key !== '') { - $enclosure->$key = $this->$key; - } - } - - $needMoreMetadataMimetypes = array(null, 'application/xhtml+xml', 'text/html'); - - if (isset($enclosure->url) && in_array(common_bare_mime($enclosure->mimetype), $needMoreMetadataMimetypes)) { - // This fetches enclosure metadata for non-local links with unset/HTML mimetypes, - // which may be enriched through oEmbed or similar (implemented as plugins) - Event::handle('FileEnclosureMetadata', array($this, &$enclosure)); - } - if (empty($enclosure->mimetype)) { - // This means we either don't know what it is, so it can't - // be shown as an enclosure, or it is an HTML link which - // does not link to a resource with further metadata. - // throw new ServerException('Unknown enclosure mimetype, not enough metadata'); - // It's not really an error that must be shown or handled... - common_debug('Unknown enclosure mimetype, not enough metadata'); - } - - self::$_enclosures[$this->getID()] = $enclosure; - return $enclosure; - } - - public function hasThumbnail() - { - try { - $this->getThumbnail(); - } catch (Exception $e) { - return false; - } - return true; - } - - /** - * Get the attachment's thumbnail record, if any or generate one. - * - * @param int|null $width Max width of thumbnail in pixels. (if null, use common_config values) - * @param int|null $height Max height of thumbnail in pixels. (if null, square-crop to $width) - * @param bool $crop Crop to the max-values' aspect ratio - * @param bool $force_still Don't allow fallback to showing original (such as animated GIF) - * @param bool|null $upscale Whether or not to scale smaller images up to larger thumbnail sizes. (null = site default) - * - * @return File_thumbnail - * - * @throws ClientException - * @throws FileNotFoundException - * @throws FileNotStoredLocallyException - * @throws InvalidFilenameException - * @throws NoResultException - * @throws ServerException on various other errors - * @throws UnsupportedMediaException if, despite trying, we can't understand how to make a thumbnail for this format - * @throws UseFileAsThumbnailException if the file is considered an image itself and should be itself as thumbnail - */ - public function getThumbnail (?int $width = null, ?int $height = null, bool $crop = false, bool $force_still = true, ?bool $upscale = null): File_thumbnail - { - return File_thumbnail::fromFileObject($this, $width, $height, $crop, $force_still, $upscale); - } - - public function getPath() - { - $filepath = self::path($this->filename); - if (!file_exists($filepath)) { - throw new FileNotFoundException($filepath); - } - return $filepath; - } - - /** - * Returns the path to either a file, or it's thumbnail if the file doesn't exist. - * This is useful in case the original file is deleted, or, as is the case for Embed - * thumbnails, we only have a thumbnail and not a file - * @param File_thumbnail|null $thumbnail - * @return string Path - * @throws FileNotFoundException - * @throws FileNotStoredLocallyException - * @throws InvalidFilenameException - * @throws ServerException - */ - public function getFileOrThumbnailPath(?File_thumbnail $thumbnail = null): string - { - if (!empty($thumbnail)) { - return $thumbnail->getPath(); - } - if (!empty($this->filename)) { - $filepath = self::path($this->filename); - if (file_exists($filepath)) { - return $filepath; - } else { - throw new FileNotFoundException($filepath); - } - } else { - try { - return File_thumbnail::byFile($this, true)->getPath(); - } catch (NoResultException $e) { - // File not stored locally - throw new FileNotStoredLocallyException($this); - } - } - } - - /** - * Return the mime type of the thumbnail if we have it, or, if not, of the File - * @param File_thumbnail|null $thumbnail - * @return string - * @throws FileNotFoundException - * @throws NoResultException - * @throws ServerException - * @throws UnsupportedMediaException - * @throws Exception - */ - public function getFileOrThumbnailMimetype(?File_thumbnail $thumbnail = null) : string - { - if (!empty($thumbnail)) { - $filepath = $thumbnail->getPath(); - } elseif (!empty($this->filename)) { - return $this->mimetype; - } else { - $filepath = File_thumbnail::byFile($this, true)->getPath(); - } - - $info = @getimagesize($filepath); - if ($info !== false) { - return $info['mime']; - } else { - throw new UnsupportedMediaException(_m("Thumbnail is not an image.")); - } - } - - /** - * Return the size of the thumbnail if we have it, or, if not, of the File - * @param File_thumbnail|null $thumbnail - * @return int - * @throws FileNotFoundException - * @throws NoResultException - * @throws ServerException - */ - public function getFileOrThumbnailSize(?File_thumbnail $thumbnail = null) : int - { - if (!empty($thumbnail)) { - return filesize($thumbnail->getPath()); - } elseif (!empty($this->filename)) { - return $this->getSize(); - } else { - return filesize(File_thumbnail::byFile($this)->getPath()); - } - } - - public function getAttachmentUrl() - { - return common_local_url('attachment', array('attachment'=>$this->getID())); - } - - public function getAttachmentDownloadUrl() - { - return common_local_url('attachment_download', ['filehash' => $this->filehash]); - } - - public function getAttachmentViewUrl() - { - return common_local_url('attachment_view', ['filehash' => $this->filehash]); - } - - /** - * @param bool|null $use_local true means require local, null means prefer original, false means use whatever is stored - * @return string - * @throws FileNotStoredLocallyException - */ - public function getUrl(?bool $use_local=null): ?string - { - if ($use_local !== false) { - if (empty($this->url)) { - // A locally stored file, so let's generate a URL for our instance. - return $this->getAttachmentViewUrl(); - } - if ($use_local) { - // if the file isn't ours but and we require a local URL anyway - throw new FileNotStoredLocallyException($this); - } - } - - // The original file's URL - return $this->url; - } - - public static function getByUrl($url) - { - $file = new File(); - $file->urlhash = self::hashurl($url); - if (!$file->find(true)) { - throw new NoResultException($file); - } - return $file; - } - - /** - * @param string $hashstr String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)' - * @return File - * @throws NoResultException - */ - public static function getByHash($hashstr) - { - $file = new File(); - $file->filehash = strtolower($hashstr); - if (!$file->find()) { - throw new NoResultException($file); - } - return $file; - } - - public function updateUrl($url) - { - $file = File::getKV('urlhash', self::hashurl($url)); - if ($file instanceof File) { - throw new ServerException('URL already exists in DB'); - } - $result = $this->query(sprintf( - <<<'END' - UPDATE %1$s - SET urlhash = %2$s, url = %3$s, modified = CURRENT_TIMESTAMP - WHERE urlhash = %4$s; - END, - $this->tableName(), - $this->_quote((string)self::hashurl($url)), - $this->_quote((string)$url), - $this->_quote((string)$this->urlhash) - )); - if ($result === false) { - common_log_db_error($this, 'UPDATE', __FILE__); - throw new ServerException("Could not UPDATE {$this->tableName()}.url"); - } - - return $result; - } - - /** - * Blow the cache of notices that link to this URL - * - * @param boolean $last Whether to blow the "last" cache too - * - * @return void - */ - - public function blowCache($last=false) - { - self::blow('file:notice-ids:%s', $this->id); - if ($last) { - self::blow('file:notice-ids:%s;last', $this->id); - } - self::blow('file:notice-count:%d', $this->id); - } - - /** - * Stream of notices linking to this URL - * - * @param integer $offset Offset to show; default is 0 - * @param integer $limit Limit of notices to show - * @param integer $since_id Since this notice - * @param integer $max_id Before this notice - * - * @return array ids of notices that link to this file - */ - - public function stream($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) - { - // FIXME: Try to get the Profile::current() here in some other way to avoid mixing - // the current session user with possibly background/queue processing. - $stream = new FileNoticeStream($this, Profile::current()); - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - public function noticeCount() - { - $cacheKey = sprintf('file:notice-count:%d', $this->id); - - $count = self::cacheGet($cacheKey); - - if ($count === false) { - $f2p = new File_to_post(); - - $f2p->file_id = $this->id; - - $count = $f2p->count(); - - self::cacheSet($cacheKey, $count); - } - - return $count; - } - - // A file with no url and with filename is a local file. - public function isLocal(): bool - { - return empty($this->url) && !empty($this->filename); - } - - // A file with an url but no filename is a remote file that wasn't fetched, not even the thumbnail. - public function isNonFetchedRemoteFile(): bool - { - return !empty($this->url) && empty($this->filename); - } - - // A file with an url and filename is a fetched remote file (maybe just a thumbnail of it). - public function isFetchedRemoteFile(): bool - { - return !empty($this->url) && !empty($this->filename); - } - - // A file with no filename nor url is a redirect. - public function isRedirect(): bool - { - return empty($this->url) && empty($this->filename); - } - - // Is in a remote location. - public function isStoredRemotely(): bool - { - return empty($this->filename); - } - - public function unlink() { - // Delete the file, if it exists locally - if (!empty($this->filename) && file_exists(self::path($this->filename))) { - $deleted = @unlink(self::path($this->filename)); - if (!$deleted) { - common_log(LOG_ERR, sprintf('Could not unlink existing file: "%s"', self::path($this->filename))); - } - } - } - - public function delete($useWhere=false) - { - // Delete the file, if it exists locally - $this->unlink(); - - // Clear out related things in the database and filesystem, such as thumbnails - $related = [ - 'File_redirection', - 'File_thumbnail', - 'File_to_post' -======= - // AUTOCODE BEGIN - - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { @@ -919,7 +65,6 @@ class File 'indexes' => [ 'file_filehash_idx' => ['filehash'], ], ->>>>>>> ecc5139ce5 ([DATABASE] Extracted schemaDef method from old files and refactored onto new files) ]; } } diff --git a/src/Entity/FileRedirection.php b/src/Entity/FileRedirection.php index 3382ee1403..d27be3c68c 100644 --- a/src/Entity/FileRedirection.php +++ b/src/Entity/FileRedirection.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class FileRedirection { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/FileThumbnail.php b/src/Entity/FileThumbnail.php index ce7d248b15..afbbf5767c 100644 --- a/src/Entity/FileThumbnail.php +++ b/src/Entity/FileThumbnail.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class FileThumbnail { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/FileToPost.php b/src/Entity/FileToPost.php index f92adb853f..3487f185d7 100644 --- a/src/Entity/FileToPost.php +++ b/src/Entity/FileToPost.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class FileToPost { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ForeignLink.php b/src/Entity/ForeignLink.php index 9e6eb0f046..5086b27d61 100644 --- a/src/Entity/ForeignLink.php +++ b/src/Entity/ForeignLink.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ForeignLink { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ForeignService.php b/src/Entity/ForeignService.php index 97c0c685f1..0af276acef 100644 --- a/src/Entity/ForeignService.php +++ b/src/Entity/ForeignService.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ForeignService { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ForeignSubscription.php b/src/Entity/ForeignSubscription.php index d7a3d45aab..fed6f7ce67 100644 --- a/src/Entity/ForeignSubscription.php +++ b/src/Entity/ForeignSubscription.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ForeignSubscription { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ForeignUser.php b/src/Entity/ForeignUser.php index 2c7b7a6782..29df3dffef 100644 --- a/src/Entity/ForeignUser.php +++ b/src/Entity/ForeignUser.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ForeignUser { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/GroupAlias.php b/src/Entity/GroupAlias.php index af66f612a6..d16bdbf23f 100644 --- a/src/Entity/GroupAlias.php +++ b/src/Entity/GroupAlias.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class GroupAlias { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/GroupBlock.php b/src/Entity/GroupBlock.php index ef262e92db..c91b9a1066 100644 --- a/src/Entity/GroupBlock.php +++ b/src/Entity/GroupBlock.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class GroupBlock { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/GroupInbox.php b/src/Entity/GroupInbox.php index 4cfba37425..c409df4ec3 100644 --- a/src/Entity/GroupInbox.php +++ b/src/Entity/GroupInbox.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class GroupInbox { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/GroupJoinQueue.php b/src/Entity/GroupJoinQueue.php index 33f42f1cd2..07be46f0be 100644 --- a/src/Entity/GroupJoinQueue.php +++ b/src/Entity/GroupJoinQueue.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class GroupJoinQueue { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/GroupMember.php b/src/Entity/GroupMember.php index a5bf1c2e3b..7f28cc7f37 100644 --- a/src/Entity/GroupMember.php +++ b/src/Entity/GroupMember.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class GroupMember { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Invitation.php b/src/Entity/Invitation.php index 5a5a653e58..5fa67691f1 100644 --- a/src/Entity/Invitation.php +++ b/src/Entity/Invitation.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Invitation { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/LocalGroup.php b/src/Entity/LocalGroup.php index 504fc518d0..4be667c9ee 100644 --- a/src/Entity/LocalGroup.php +++ b/src/Entity/LocalGroup.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class LocalGroup { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/LocationNamespace.php b/src/Entity/LocationNamespace.php index 8786b61bf4..1fc03223cb 100644 --- a/src/Entity/LocationNamespace.php +++ b/src/Entity/LocationNamespace.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class LocationNamespace { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/LoginToken.php b/src/Entity/LoginToken.php index dc25468256..14da860745 100644 --- a/src/Entity/LoginToken.php +++ b/src/Entity/LoginToken.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class LoginToken { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Nonce.php b/src/Entity/Nonce.php index c47a785a1b..d513dcb6c2 100644 --- a/src/Entity/Nonce.php +++ b/src/Entity/Nonce.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Nonce { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Notice.php b/src/Entity/Notice.php index 87d76b2557..dc12527926 100644 --- a/src/Entity/Notice.php +++ b/src/Entity/Notice.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Notice { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/NoticeLocation.php b/src/Entity/NoticeLocation.php index 2e8c0cce92..02c6244788 100644 --- a/src/Entity/NoticeLocation.php +++ b/src/Entity/NoticeLocation.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class NoticeLocation { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/NoticePrefs.php b/src/Entity/NoticePrefs.php index 790ee4cf93..b5cb0645f0 100644 --- a/src/Entity/NoticePrefs.php +++ b/src/Entity/NoticePrefs.php @@ -37,9 +37,9 @@ namespace App\Entity; */ class NoticePrefs { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/NoticeSource.php b/src/Entity/NoticeSource.php index 371bc16688..a40e5f7c90 100644 --- a/src/Entity/NoticeSource.php +++ b/src/Entity/NoticeSource.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class NoticeSource { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/NoticeTag.php b/src/Entity/NoticeTag.php index ba73132ea1..d0bb5af4e7 100644 --- a/src/Entity/NoticeTag.php +++ b/src/Entity/NoticeTag.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class NoticeTag { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/OauthApplication.php b/src/Entity/OauthApplication.php index 2e4fd15888..9493448ba3 100644 --- a/src/Entity/OauthApplication.php +++ b/src/Entity/OauthApplication.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class OauthApplication { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/OauthApplicationUser.php b/src/Entity/OauthApplicationUser.php index 61345ee1b4..db8af03114 100644 --- a/src/Entity/OauthApplicationUser.php +++ b/src/Entity/OauthApplicationUser.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class OauthApplicationUser { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/OauthTokenAssociation.php b/src/Entity/OauthTokenAssociation.php index fa0090d0d0..a7576a59fd 100644 --- a/src/Entity/OauthTokenAssociation.php +++ b/src/Entity/OauthTokenAssociation.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class OauthTokenAssociation { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/OldSchoolPrefs.php b/src/Entity/OldSchoolPrefs.php index 7f3c0e247b..ac5a3edb61 100644 --- a/src/Entity/OldSchoolPrefs.php +++ b/src/Entity/OldSchoolPrefs.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class OldSchoolPrefs { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Profile.php b/src/Entity/Profile.php index 9ddf357e4c..c88d0055ad 100644 --- a/src/Entity/Profile.php +++ b/src/Entity/Profile.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Profile { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ProfileBlock.php b/src/Entity/ProfileBlock.php index 5b82cf4315..f128521864 100644 --- a/src/Entity/ProfileBlock.php +++ b/src/Entity/ProfileBlock.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ProfileBlock { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ProfileList.php b/src/Entity/ProfileList.php index 68713d0655..66c96e8113 100644 --- a/src/Entity/ProfileList.php +++ b/src/Entity/ProfileList.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ProfileList { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ProfilePrefs.php b/src/Entity/ProfilePrefs.php index dc95346b61..7351028906 100644 --- a/src/Entity/ProfilePrefs.php +++ b/src/Entity/ProfilePrefs.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ProfilePrefs { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ProfileRole.php b/src/Entity/ProfileRole.php index 00603e2b89..1367c3344b 100644 --- a/src/Entity/ProfileRole.php +++ b/src/Entity/ProfileRole.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ProfileRole { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ProfileTag.php b/src/Entity/ProfileTag.php index bbb9c391bd..f59108440e 100644 --- a/src/Entity/ProfileTag.php +++ b/src/Entity/ProfileTag.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ProfileTag { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/ProfileTagSubscription.php b/src/Entity/ProfileTagSubscription.php index bbc0b98db8..c3729546ba 100644 --- a/src/Entity/ProfileTagSubscription.php +++ b/src/Entity/ProfileTagSubscription.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class ProfileTagSubscription { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/QueueItem.php b/src/Entity/QueueItem.php index 6ed31b2e15..a720955df5 100644 --- a/src/Entity/QueueItem.php +++ b/src/Entity/QueueItem.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class QueueItem { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/RelatedGroup.php b/src/Entity/RelatedGroup.php index f4ba2dc8ac..1bfba6ce20 100644 --- a/src/Entity/RelatedGroup.php +++ b/src/Entity/RelatedGroup.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class RelatedGroup { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/RememberMe.php b/src/Entity/RememberMe.php index 21752011d7..21c43fe351 100644 --- a/src/Entity/RememberMe.php +++ b/src/Entity/RememberMe.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class RememberMe { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Reply.php b/src/Entity/Reply.php index 8650a01926..c0d60d31a2 100644 --- a/src/Entity/Reply.php +++ b/src/Entity/Reply.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Reply { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/SchemaVersion.php b/src/Entity/SchemaVersion.php index 1319f57458..6e5c1562ef 100644 --- a/src/Entity/SchemaVersion.php +++ b/src/Entity/SchemaVersion.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class SchemaVersion { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Session.php b/src/Entity/Session.php index 319b846ba2..94b6b55477 100644 --- a/src/Entity/Session.php +++ b/src/Entity/Session.php @@ -37,9 +37,9 @@ namespace App\Entity; */ class Session { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/SmsCarrier.php b/src/Entity/SmsCarrier.php index 6e2ea7cb6f..72e767e2ee 100644 --- a/src/Entity/SmsCarrier.php +++ b/src/Entity/SmsCarrier.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class SmsCarrier { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Subscription.php b/src/Entity/Subscription.php index abfda76ebe..9e81bf9ccb 100644 --- a/src/Entity/Subscription.php +++ b/src/Entity/Subscription.php @@ -35,431 +35,10 @@ namespace App\Entity; */ class Subscription { -<<<<<<< HEAD - const CACHE_WINDOW = 201; - const FORCE = true; + // {{{ Autocode +>>>>>>> e63aa4d971 ([TOOLS] Change autocode tag to allow editor folding) - public $__table = 'subscription'; // table name - public $subscriber; // int(4) primary_key not_null - public $subscribed; // int(4) primary_key not_null - public $jabber; // bool default_true - public $sms; // bool default_true - public $token; // varchar(191) not 255 because utf8mb4 takes more space - public $secret; // varchar(191) not 255 because utf8mb4 takes more space - public $uri; // varchar(191) not 255 because utf8mb4 takes more space - public $created; // datetime() - public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - - public static function schemaDef() - { - return array( - 'fields' => array( - 'subscriber' => array('type' => 'int', 'not null' => true, 'description' => 'profile listening'), - 'subscribed' => array('type' => 'int', 'not null' => true, 'description' => 'profile being listened to'), - 'jabber' => array('type' => 'bool', 'default' => true, 'description' => 'deliver jabber messages'), - 'sms' => array('type' => 'bool', 'default' => true, 'description' => 'deliver sms messages'), - 'token' => array('type' => 'varchar', 'length' => 191, 'description' => 'authorization token'), - 'secret' => array('type' => 'varchar', 'length' => 191, 'description' => 'token secret'), - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique 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('subscriber', 'subscribed'), - 'unique keys' => array( - 'subscription_uri_key' => array('uri'), - ), - 'indexes' => array( - 'subscription_subscriber_created_subscribed_idx' => array('subscriber', 'created', 'subscribed'), - 'subscription_subscribed_created_subscriber_idx' => array('subscribed', 'created', 'subscriber'), - 'subscription_token_idx' => array('token'), - ), - ); - } - - /** - * Make a new subscription - * - * @param Profile $subscriber party to receive new notices - * @param Profile $other party sending notices; publisher - * @param bool $force pass Subscription::FORCE to override local subscription approval - * - * @return mixed Subscription or Subscription_queue: new subscription info - */ - - public static function start(Profile $subscriber, Profile $other, $force = false) - { - if (!$subscriber->hasRight(Right::SUBSCRIBE)) { - // TRANS: Exception thrown when trying to subscribe while being banned from subscribing. - throw new Exception(_('You have been banned from subscribing.')); - } - - if (self::exists($subscriber, $other)) { - // TRANS: Exception thrown when trying to subscribe while already subscribed. - throw new AlreadyFulfilledException(_('Already subscribed!')); - } - - if ($other->hasBlocked($subscriber)) { - // TRANS: Exception thrown when trying to subscribe to a user who has blocked the subscribing user. - throw new Exception(_('User has blocked you.')); - } - - if (Event::handle('StartSubscribe', array($subscriber, $other))) { - // unless subscription is forced, the user policy for subscription approvals is tested - if (!$force && $other->requiresSubscriptionApproval($subscriber)) { - try { - $sub = Subscription_queue::saveNew($subscriber, $other); - $sub->notify(); - } catch (AlreadyFulfilledException $e) { - $sub = Subscription_queue::getSubQueue($subscriber, $other); - } - } else { - $otherUser = User::getKV('id', $other->id); - $sub = self::saveNew($subscriber, $other); - $sub->notify(); - - self::blow('user:notices_with_friends:%d', $subscriber->id); - - self::blow('subscription:by-subscriber:'.$subscriber->id); - self::blow('subscription:by-subscribed:'.$other->id); - - $subscriber->blowSubscriptionCount(); - $other->blowSubscriberCount(); - - if ($otherUser instanceof User && - $otherUser->autosubscribe && - !self::exists($other, $subscriber) && - !$subscriber->hasBlocked($other)) { - try { - self::start($other, $subscriber); - } catch (AlreadyFulfilledException $e) { - // This shouldn't happen due to !self::exists above - common_debug('Tried to autosubscribe a user to its new subscriber.'); - } catch (Exception $e) { - common_log(LOG_ERR, "Exception during autosubscribe of {$other->nickname} to profile {$subscriber->id}: {$e->getMessage()}"); - } - } - } - - if ($sub instanceof Subscription) { // i.e. not Subscription_queue - Event::handle('EndSubscribe', array($subscriber, $other)); - } - } - - return $sub; - } - - public static function ensureStart(Profile $subscriber, Profile $other, $force = false) - { - try { - $sub = self::start($subscriber, $other, $force); - } catch (AlreadyFulfilledException $e) { - return self::getSubscription($subscriber, $other); - } - return $sub; - } - - /** - * Low-level subscription save. - * Outside callers should use Subscription::start() - */ - protected static function saveNew(Profile $subscriber, Profile $other) - { - $sub = new Subscription(); - - $sub->subscriber = $subscriber->getID(); - $sub->subscribed = $other->getID(); - $sub->jabber = true; - $sub->sms = true; - $sub->created = common_sql_now(); - $sub->uri = self::newUri( - $subscriber, - $other, - $sub->created - ); - - $result = $sub->insert(); - - if ($result===false) { - common_log_db_error($sub, 'INSERT', __FILE__); - // TRANS: Exception thrown when a subscription could not be stored on the server. - throw new Exception(_('Could not save subscription.')); - } - - return $sub; - } - - public function notify() - { - // XXX: add other notifications (Jabber, SMS) here - // XXX: queue this and handle it offline - // XXX: Whatever happens, do it in Twitter-like API, too - - $this->notifyEmail(); - } - - public function notifyEmail() - { - $subscribedUser = User::getKV('id', $this->subscribed); - - if ($subscribedUser instanceof User) { - $subscriber = Profile::getKV('id', $this->subscriber); - - mail_subscribe_notify_profile($subscribedUser, $subscriber); - } - } - - /** - * Cancel a subscription - * - */ - public static function cancel(Profile $subscriber, Profile $other) - { - if (!self::exists($subscriber, $other)) { - // TRANS: Exception thrown when trying to unsibscribe without a subscription. - throw new AlreadyFulfilledException(_('Not subscribed!')); - } - - // Don't allow deleting self subs - - if ($subscriber->id == $other->id) { - // TRANS: Exception thrown when trying to unsubscribe a user from themselves. - throw new Exception(_('Could not delete self-subscription.')); - } - - if (Event::handle('StartUnsubscribe', array($subscriber, $other))) { - $sub = Subscription::pkeyGet(array('subscriber' => $subscriber->id, - 'subscribed' => $other->id)); - - // note we checked for existence above - - assert(!empty($sub)); - - $result = $sub->delete(); - - if (!$result) { - common_log_db_error($sub, 'DELETE', __FILE__); - // TRANS: Exception thrown when a subscription could not be deleted on the server. - throw new Exception(_('Could not delete subscription.')); - } - - self::blow('user:notices_with_friends:%d', $subscriber->id); - - self::blow('subscription:by-subscriber:'.$subscriber->id); - self::blow('subscription:by-subscribed:'.$other->id); - - $subscriber->blowSubscriptionCount(); - $other->blowSubscriberCount(); - - Event::handle('EndUnsubscribe', array($subscriber, $other)); - } - - return; - } - - public static function exists(Profile $subscriber, Profile $other) - { - try { - $sub = self::getSubscription($subscriber, $other); - } catch (NoResultException $e) { - return false; - } - - return true; - } - - public static function getSubscription(Profile $subscriber, Profile $other) - { - // This is essentially a pkeyGet but we have an object to return in NoResultException - $sub = new Subscription(); - $sub->subscriber = $subscriber->id; - $sub->subscribed = $other->id; - if (!$sub->find(true)) { - throw new NoResultException($sub); - } - return $sub; - } - - public function getSubscriber() - { - return Profile::getByID($this->subscriber); - } - - public function getSubscribed() - { - return Profile::getByID($this->subscribed); - } - - public function asActivity() - { - $subscriber = $this->getSubscriber(); - $subscribed = $this->getSubscribed(); - - $act = new Activity(); - - $act->verb = ActivityVerb::FOLLOW; - - // XXX: rationalize this with the URL - - $act->id = $this->getUri(); - - $act->time = strtotime($this->created); - // TRANS: Activity title when subscribing to another person. - $act->title = _m('TITLE', 'Follow'); - // TRANS: Notification given when one person starts following another. - // TRANS: %1$s is the subscriber, %2$s is the subscribed. - $act->content = sprintf( - _('%1$s is now following %2$s.'), - $subscriber->getBestName(), - $subscribed->getBestName() - ); - - $act->actor = $subscriber->asActivityObject(); - $act->objects[] = $subscribed->asActivityObject(); - - $url = common_local_url( - 'AtomPubShowSubscription', - [ - 'subscriber' => $subscriber->id, - 'subscribed' => $subscribed->id, - ] - ); - - $act->selfLink = $url; - $act->editLink = $url; - - return $act; - } - - /** - * Stream of subscriptions with the same subscriber - * - * Useful for showing pages that list subscriptions in reverse - * chronological order. Has offset & limit to make paging - * easy. - * - * @param integer $profile_id ID of the subscriber profile - * @param integer $offset Offset from latest - * @param integer $limit Maximum number to fetch - * - * @return Subscription stream of subscriptions; use fetch() to iterate - */ - public static function bySubscriber($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE) - { - // "by subscriber" means it is the list of subscribed users we want - $ids = self::getSubscribedIDs($profile_id, $offset, $limit); - return Subscription::listFind('subscribed', $ids); - } - - /** - * Stream of subscriptions with the same subscriber - * - * Useful for showing pages that list subscriptions in reverse - * chronological order. Has offset & limit to make paging - * easy. - * - * @param integer $profile_id ID of the subscribed profile - * @param integer $offset Offset from latest - * @param integer $limit Maximum number to fetch - * - * @return Subscription stream of subscriptions; use fetch() to iterate - */ - public static function bySubscribed($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE) - { - // "by subscribed" means it is the list of subscribers we want - $ids = self::getSubscriberIDs($profile_id, $offset, $limit); - return Subscription::listFind('subscriber', $ids); - } - - - // The following are helper functions to the subscription lists, - // notably the public ones get used in places such as Profile - public static function getSubscribedIDs($profile_id, $offset, $limit) - { - return self::getSubscriptionIDs('subscribed', $profile_id, $offset, $limit); - } - - public static function getSubscriberIDs($profile_id, $offset, $limit) - { - return self::getSubscriptionIDs('subscriber', $profile_id, $offset, $limit); - } - - private static function getSubscriptionIDs($get_type, $profile_id, $offset, $limit) - { - switch ($get_type) { - case 'subscribed': - $by_type = 'subscriber'; - break; - case 'subscriber': - $by_type = 'subscribed'; - break; - default: - throw new Exception('Bad type argument to getSubscriptionIDs'); - } - - $cacheKey = 'subscription:by-'.$by_type.':'.$profile_id; - - $queryoffset = $offset; - $querylimit = $limit; - - if ($offset + $limit <= self::CACHE_WINDOW) { - // Oh, it seems it should be cached - $ids = self::cacheGet($cacheKey); - if (is_array($ids)) { - return array_slice($ids, $offset, $limit); - } - // Being here indicates we didn't find anything cached - // so we'll have to fill it up simultaneously - $queryoffset = 0; - $querylimit = self::CACHE_WINDOW; - } - - $sub = new Subscription(); - $sub->$by_type = $profile_id; - $sub->selectAdd($get_type); - $sub->whereAdd($get_type . ' <> ' . $profile_id); - $sub->orderBy("created DESC, {$get_type} DESC"); - $sub->limit($queryoffset, $querylimit); - - if (!$sub->find()) { - return array(); - } - - $ids = $sub->fetchAll($get_type); - - // If we're simultaneously filling up cache, remember to slice - if ($queryoffset === 0 && $querylimit === self::CACHE_WINDOW) { - self::cacheSet($cacheKey, $ids); - return array_slice($ids, $offset, $limit); - } - - return $ids; - } - - /** - * Flush cached subscriptions when subscription is updated - * - * Because we cache subscriptions, it's useful to flush them - * here. - * - * @param mixed $dataObject Original version of object - * - * @return boolean success flag. - */ - public function update($dataObject = false) - { - self::blow('subscription:by-subscriber:'.$this->subscriber); - self::blow('subscription:by-subscribed:'.$this->subscribed); - - return parent::update($dataObject); - } - - public function getUri() - { - return $this->uri ?: self::newUri($this->getSubscriber(), $this->getSubscribed(), $this->created); -======= - // AUTOCODE BEGIN - - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { @@ -486,6 +65,5 @@ class Subscription '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 index 68147a2a82..a99a165814 100644 --- a/src/Entity/SubscriptionQueue.php +++ b/src/Entity/SubscriptionQueue.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class SubscriptionQueue { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/Token.php b/src/Entity/Token.php index 1548b5acdd..4853509d6c 100644 --- a/src/Entity/Token.php +++ b/src/Entity/Token.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class Token { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/UnavailableStatusNetwork.php b/src/Entity/UnavailableStatusNetwork.php index 61b428cb93..ba315a899f 100644 --- a/src/Entity/UnavailableStatusNetwork.php +++ b/src/Entity/UnavailableStatusNetwork.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class UnavailableStatusNetwork { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/User.php b/src/Entity/User.php index 13d92fe162..d25b5e2357 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -35,1034 +35,15 @@ namespace App\Entity; */ class User { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): 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'), - 'password' => array('type' => 'text', 'description' => 'salted password, can be null for OpenID users'), - 'email' => array('type' => 'varchar', 'length' => 191, 'description' => 'email address for password recovery etc.'), - 'incomingemail' => array('type' => 'varchar', 'length' => 191, 'description' => 'email address for post-by-email'), - 'emailnotifysub' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of subscriptions'), - 'emailnotifyfav' => array('type' => 'int', 'size' => 'tiny', 'default' => null, 'description' => 'Notify by email of favorites'), - 'emailnotifynudge' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of nudges'), - 'emailnotifymsg' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of direct messages'), - 'emailnotifyattn' => array('type' => 'bool', 'default' => true, 'description' => 'Notify by email of @-replies'), - 'language' => array('type' => 'varchar', 'length' => 50, 'description' => 'preferred language'), - 'timezone' => array('type' => 'varchar', 'length' => 50, 'description' => 'timezone'), - 'emailpost' => array('type' => 'bool', 'default' => true, 'description' => 'Post by email'), - 'sms' => array('type' => 'varchar', 'length' => 64, 'description' => 'sms phone number'), - 'carrier' => array('type' => 'int', 'description' => 'foreign key to sms_carrier'), - 'smsnotify' => array('type' => 'bool', 'default' => false, 'description' => 'whether to send notices to SMS'), - 'smsreplies' => array('type' => 'bool', 'default' => false, 'description' => 'whether to send notices to SMS on replies'), - 'smsemail' => array('type' => 'varchar', 'length' => 191, 'description' => 'built from sms and carrier'), - 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'), - 'autosubscribe' => array('type' => 'bool', 'default' => false, 'description' => 'automatically subscribe to users who subscribe to us'), - 'subscribe_policy' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => '0 = anybody can subscribe; 1 = require approval'), - 'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'), - 'private_stream' => array('type' => 'bool', 'default' => false, 'description' => 'whether to limit all notices to followers only'), - '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( - 'user_nickname_key' => array('nickname'), - 'user_email_key' => array('email'), - 'user_incomingemail_key' => array('incomingemail'), - 'user_sms_key' => array('sms'), - 'user_uri_key' => array('uri'), - ), - 'foreign keys' => array( - 'user_id_fkey' => array('profile', array('id' => 'id')), - 'user_carrier_fkey' => array('sms_carrier', array('carrier' => 'id')), - ), - 'indexes' => array( - 'user_carrier_idx' => array('carrier'), - 'user_created_id_idx' => array('created', 'id'), - 'user_smsemail_idx' => array('smsemail'), - ), - ); - } - - protected $_profile = array(); - - /** - * @return Profile - * - * @throws UserNoProfileException if user has no profile - */ - public function getProfile() - { - if (!isset($this->_profile[$this->id])) { - $profile = Profile::getKV('id', $this->id); - if (!$profile instanceof Profile) { - throw new UserNoProfileException($this); - } - $this->_profile[$this->id] = $profile; - } - return $this->_profile[$this->id]; - } - - public function sameAs(Profile $other) - { - return $this->getProfile()->sameAs($other); - } - - public function getUri() - { - return $this->uri; - } - - public function getNickname() - { - return $this->getProfile()->getNickname(); - } - - public static function getByNickname($nickname) - { - $user = User::getKV('nickname', $nickname); - if (!$user instanceof User) { - throw new NoSuchUserException(array('nickname' => $nickname)); - } - - return $user; - } - - public function isSubscribed(Profile $other) - { - return $this->getProfile()->isSubscribed($other); - } - - public function hasPendingSubscription(Profile $other) - { - return $this->getProfile()->hasPendingSubscription($other); - } - - /** - * Get the most recent notice posted by this user, if any. - * - * @return mixed Notice or null - */ - public function getCurrentNotice() - { - return $this->getProfile()->getCurrentNotice(); - } - - public function getCarrier() - { - return Sms_carrier::getKV('id', $this->carrier); - } - - public function hasBlocked(Profile $other) - { - return $this->getProfile()->hasBlocked($other); - } - - /** - * Register a new user account and profile and set up default subscriptions. - * If a new-user welcome message is configured, this will be sent. - * - * @param array $fields associative array of optional properties - * string 'bio' - * string 'email' - * bool 'email_confirmed' pass true to mark email as pre-confirmed - * string 'fullname' - * string 'homepage' - * string 'location' informal string description of geolocation - * 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 - * string 'nickname' REQUIRED - * string 'password' (may be missing for eg OpenID registrations) - * string 'code' invite code - * ?string 'uri' permalink to notice; defaults to local notice URL - * @return User object - * @throws Exception on failure - */ - public static function register(array $fields, $accept_email_fail = false) - { - // MAGICALLY put fields into current scope - extract($fields); - - $profile = new Profile(); - - if (!empty($email)) { - $email = common_canonical_email($email); - } - - // Normalize _and_ check whether it is in use. Throw NicknameException on failure. - $profile->nickname = Nickname::normalize($nickname, true); - - $profile->profileurl = common_profile_url($profile->nickname); - - if (!empty($fullname)) { - $profile->fullname = $fullname; - } - if (!empty($homepage)) { - $profile->homepage = $homepage; - } - if (!empty($bio)) { - $profile->bio = $bio; - } - if (!empty($location)) { - $profile->location = $location; - - $loc = Location::fromName($location); - - if (!empty($loc)) { - $profile->lat = $loc->lat; - $profile->lon = $loc->lon; - $profile->location_id = $loc->location_id; - $profile->location_ns = $loc->location_ns; - } - } - - $profile->created = common_sql_now(); - - $user = new User(); - - $user->nickname = $profile->nickname; - - $invite = null; - - // Users who respond to invite email have proven their ownership of that address - - if (!empty($code)) { - $invite = Invitation::getKV($code); - if ($invite instanceof Invitation && $invite->address && $invite->address_type == 'email' && $invite->address == $email) { - $user->email = $invite->address; - } - } - - if (isset($email_confirmed) && $email_confirmed) { - $user->email = $email; - } - - // Set default-on options here, otherwise they'll be disabled - // initially for sites using caching, since the initial encache - // doesn't know about the defaults in the database. - $user->emailnotifysub = true; - $user->emailnotifynudge = true; - $user->emailnotifymsg = true; - $user->emailnotifyattn = true; - $user->emailpost = true; - - $user->created = common_sql_now(); - - if (Event::handle('StartUserRegister', array($profile))) { - $profile->query('START TRANSACTION'); - - $id = $profile->insert(); - if ($id === false) { - common_log_db_error($profile, 'INSERT', __FILE__); - $profile->query('ROLLBACK'); - // TRANS: Profile data could not be inserted for some reason. - throw new ServerException(_m('Could not insert profile data for new user.')); - } - - // Necessary because id has been known to be reissued. - if ($profile->hasRole(Profile_role::DELETED)) { - $profile->revokeRole(Profile_role::DELETED); - } - - $user->id = $id; - - if (!empty($uri)) { - $user->uri = $uri; - } else { - $user->uri = common_user_uri($user); - } - - if (!empty($password)) { // may not have a password for OpenID users - $user->password = common_munge_password($password); - } - - $result = $user->insert(); - - if ($result === false) { - common_log_db_error($user, 'INSERT', __FILE__); - $profile->query('ROLLBACK'); - // TRANS: User data could not be inserted for some reason. - throw new ServerException(_m('Could not insert user data for new user.')); - } - - // Everyone is subscribed to themself - - $subscription = new Subscription(); - $subscription->subscriber = $user->id; - $subscription->subscribed = $user->id; - $subscription->created = $user->created; - - $result = $subscription->insert(); - - if (!$result) { - common_log_db_error($subscription, 'INSERT', __FILE__); - $profile->query('ROLLBACK'); - // TRANS: Subscription data could not be inserted for some reason. - throw new ServerException(_m('Could not insert subscription data for new user.')); - } - - // Mark that this invite was converted - - if (!empty($invite)) { - $invite->convert($user); - } - - if (!empty($email) && empty($user->email)) { - // The actual email will be sent further down, after the database COMMIT - - $confirm = new Confirm_address(); - $confirm->code = common_confirmation_code(128); - $confirm->user_id = $user->id; - $confirm->address = $email; - $confirm->address_type = 'email'; - - $result = $confirm->insert(); - - if ($result===false) { - common_log_db_error($confirm, 'INSERT', __FILE__); - $profile->query('ROLLBACK'); - // TRANS: Email confirmation data could not be inserted for some reason. - throw new ServerException(_m('Could not insert email confirmation data for new user.')); - } - } - - if (!empty($code) && $user->email) { - $user->emailChanged(); - } - - // Default system subscription - - $defnick = common_config('newuser', 'default'); - - if (!empty($defnick)) { - $defuser = User::getKV('nickname', $defnick); - if (empty($defuser)) { - common_log( - LOG_WARNING, - sprintf('Default user %s does not exist.', $defnick), - __FILE__ - ); - } else { - Subscription::ensureStart($profile, $defuser->getProfile()); - } - } - - $profile->query('COMMIT'); - - if (!empty($email) && empty($user->email)) { - try { - $confirm->sendConfirmation(); - } catch (EmailException $e) { - common_log(LOG_ERR, "Could not send user registration email for user id=={$profile->getID()}: {$e->getMessage()}"); - if (!$accept_email_fail) { - throw $e; - } - } - } - - // Welcome message - - $welcome = common_config('newuser', 'welcome'); - - if (!empty($welcome)) { - $welcomeuser = User::getKV('nickname', $welcome); - if (empty($welcomeuser)) { - common_log( - LOG_WARNING, - sprintf('Welcome user %s does not exist.', $defnick), - __FILE__ - ); - } else { - $notice = Notice::saveNew( - $welcomeuser->id, - // TRANS: Notice given on user registration. - // TRANS: %1$s is the sitename, $2$s is the registering user's nickname. - sprintf( - _('Welcome to %1$s, @%2$s!'), - common_config('site', 'name'), - $profile->getNickname() - ), - 'system' - ); - } - } - - Event::handle('EndUserRegister', array($profile)); - } - - if (!$user instanceof User || empty($user->id)) { - throw new ServerException('User could not be registered. Probably an event hook that failed.'); - } - - return $user; - } - - // Things we do when the email changes - public function emailChanged() - { - $invites = new Invitation(); - $invites->address = $this->email; - $invites->address_type = 'email'; - - if ($invites->find()) { - while ($invites->fetch()) { - try { - $other = Profile::getByID($invites->user_id); - Subscription::start($other, $this->getProfile()); - } catch (NoResultException $e) { - // profile did not exist - } catch (AlreadyFulfilledException $e) { - // already subscribed to this profile - } catch (Exception $e) { - common_log(LOG_ERR, 'On-invitation-completion subscription failed when subscribing '._ve($invites->user_id).' to '.$this->getProfile()->getID().': '._ve($e->getMessage())); - } - } - } - } - - public function mutuallySubscribed(Profile $other) - { - return $this->getProfile()->mutuallySubscribed($other); - } - - public function mutuallySubscribedUsers() - { - $user = new User(); - - // 3-way join; probably should get cached - $user->query(sprintf( - 'SELECT %1$s.* ' . - 'FROM subscription AS sub1 INNER JOIN %1$s ON sub1.subscribed = %1$s.id ' . - 'INNER JOIN subscription AS sub2 ON %1$s.id = sub2.subscriber ' . - 'WHERE sub1.subscriber = %2$d AND sub2.subscribed = %2$d ' . - 'ORDER BY %1$s.nickname', - $user->escapedTableName(), - $this->id - )); - - return $user; - } - - public function getReplies($offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0) - { - return $this->getProfile()->getReplies($offset, $limit, $since_id, $before_id); - } - - public function getTaggedNotices($tag, $offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0) - { - return $this->getProfile()->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id); - } - - public function getNotices($offset = 0, $limit = NOTICES_PER_PAGE, $since_id = 0, $before_id = 0) - { - return $this->getProfile()->getNotices($offset, $limit, $since_id, $before_id); - } - - public function block(Profile $other) - { - // Add a new block record - - // no blocking (and thus unsubbing from) yourself - - if ($this->id == $other->id) { - common_log( - LOG_WARNING, - sprintf( - "Profile ID %d (%s) tried to block themself.", - $this->id, - $this->nickname - ) - ); - return false; - } - - $block = new Profile_block(); - - $block->query('START TRANSACTION'); - - $block->blocker = $this->id; - $block->blocked = $other->id; - - $result = $block->insert(); - - if (!$result) { - common_log_db_error($block, 'INSERT', __FILE__); - return false; - } - - $self = $this->getProfile(); - if (Subscription::exists($other, $self)) { - Subscription::cancel($other, $self); - } - if (Subscription::exists($self, $other)) { - Subscription::cancel($self, $other); - } - - $block->query('COMMIT'); - - return true; - } - - public function unblock(Profile $other) - { - // Get the block record - - $block = Profile_block::exists($this->getProfile(), $other); - - if (!$block) { - return false; - } - - $result = $block->delete(); - - if (!$result) { - common_log_db_error($block, 'DELETE', __FILE__); - return false; - } - - return true; - } - - public function isMember(User_group $group) - { - return $this->getProfile()->isMember($group); - } - - public function isAdmin(User_group $group) - { - return $this->getProfile()->isAdmin($group); - } - - public function getGroups($offset = 0, $limit = null) - { - return $this->getProfile()->getGroups($offset, $limit); - } - - /** - * Request to join the given group. - * May throw exceptions on failure. - * - * @param User_group $group - * @return Group_member - */ - public function joinGroup(User_group $group) - { - return $this->getProfile()->joinGroup($group); - } - - /** - * Leave a group that this user is a member of. - * - * @param User_group $group - */ - public function leaveGroup(User_group $group) - { - return $this->getProfile()->leaveGroup($group); - } - - public function getSubscribed($offset = 0, $limit = null) - { - return $this->getProfile()->getSubscribed($offset, $limit); - } - - public function getSubscribers($offset = 0, $limit = null) - { - return $this->getProfile()->getSubscribers($offset, $limit); - } - - public function getTaggedSubscribers($tag, $offset = 0, $limit = null) - { - return $this->getProfile()->getTaggedSubscribers($tag, $offset, $limit); - } - - public function getTaggedSubscriptions($tag, $offset = 0, $limit = null) - { - return $this->getProfile()->getTaggedSubscriptions($tag, $offset, $limit); - } - - public function hasRight($right) - { - return $this->getProfile()->hasRight($right); - } - - public function delete($useWhere = false) - { - if (empty($this->id)) { - common_log(LOG_WARNING, "Ambiguous User->delete(); skipping related tables."); - return parent::delete($useWhere); - } - - try { - if (!$this->hasRole(Profile_role::DELETED)) { - $profile = $this->getProfile(); - $profile->delete(); - } - } catch (UserNoProfileException $unp) { - common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion."); - } - - $related = array( - 'Confirm_address', - 'Remember_me', - 'Foreign_link', - 'Invitation', - ); - - Event::handle('UserDeleteRelated', array($this, &$related)); - - foreach ($related as $cls) { - $inst = new $cls(); - $inst->user_id = $this->id; - $inst->delete(); - } - - $this->_deleteTags(); - $this->_deleteBlocks(); - - return parent::delete($useWhere); - } - - public function _deleteTags() - { - $tag = new Profile_tag(); - $tag->tagger = $this->id; - $tag->delete(); - } - - public function _deleteBlocks() - { - $block = new Profile_block(); - $block->blocker = $this->id; - $block->delete(); - // XXX delete group block? Reset blocker? - } - - public function hasRole($name) - { - return $this->getProfile()->hasRole($name); - } - - public function grantRole($name) - { - return $this->getProfile()->grantRole($name); - } - - public function revokeRole($name) - { - return $this->getProfile()->revokeRole($name); - } - - public function isSandboxed() - { - return $this->getProfile()->isSandboxed(); - } - - public function isSilenced() - { - return $this->getProfile()->isSilenced(); - } - - public function receivesEmailNotifications() - { - // We could do this in one large if statement, but that's not as easy to read - // Don't send notifications if we don't know the user's email address or it is - // explicitly undesired by the user's own settings. - if (empty($this->email) || !$this->emailnotifyattn) { - return false; - } - // Don't send notifications to a user who is sandboxed or silenced - if ($this->isSandboxed() || $this->isSilenced()) { - return false; - } - return true; - } - - public function repeatedByMe($offset = 0, $limit = 20, $since_id = null, $max_id = null) - { - // FIXME: Use another way to get Profile::current() since we - // want to avoid confusion between session user and queue processing. - $stream = new RepeatedByMeNoticeStream($this->getProfile(), Profile::current()); - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - - public function repeatsOfMe($offset = 0, $limit = 20, $since_id = null, $max_id = null) - { - // FIXME: Use another way to get Profile::current() since we - // want to avoid confusion between session user and queue processing. - $stream = new RepeatsOfMeNoticeStream($this->getProfile(), Profile::current()); - return $stream->getNotices($offset, $limit, $since_id, $max_id); - } - - public function repeatedToMe($offset = 0, $limit = 20, $since_id = null, $max_id = null) - { - return $this->getProfile()->repeatedToMe($offset, $limit, $since_id, $max_id); - } - - public static function siteOwner() - { - $owner = self::cacheGet('user:site_owner'); - - if ($owner === false) { // cache miss - - $pr = new Profile_role(); - $pr->role = Profile_role::OWNER; - $pr->orderBy('created, profile_id'); - $pr->limit(1); - - if (!$pr->find(true)) { - throw new NoResultException($pr); - } - - $owner = User::getKV('id', $pr->profile_id); - - self::cacheSet('user:site_owner', $owner); - } - - if ($owner instanceof User) { - return $owner; - } - - throw new ServerException(_('No site owner configured.')); - } - - /** - * Pull the primary site account to use in single-user mode. - * If a valid user nickname is listed in 'singleuser':'nickname' - * in the config, this will be used; otherwise the site owner - * account is taken by default. - * - * @return User - * @throws ServerException if no valid single user account is present - * @throws ServerException if called when not in single-user mode - */ - public static function singleUser() - { - if (!common_config('singleuser', 'enabled')) { - // TRANS: Server exception. - throw new ServerException(_('Single-user mode code called when not enabled.')); - } - - if (!empty($nickname = common_config('singleuser', 'nickname'))) { - $user = User::getKV('nickname', $nickname); - if ($user instanceof User) { - return $user; - } - } - - // If there was no nickname or no user by that nickname, - // try the site owner. Throws exception if not configured. - return User::siteOwner(); - } - - /** - * This is kind of a hack for using external setup code that's trying to - * build single-user sites. - * - * Will still return a username if the config singleuser/nickname is set - * even if the account doesn't exist, which normally indicates that the - * site is horribly misconfigured. - * - * At the moment, we need to let it through so that router setup can - * complete, otherwise we won't be able to create the account. - * - * This will be easier when we can more easily create the account and - * *then* switch the site to 1user mode without jumping through hoops. - * - * @return string - * @throws ServerException if no valid single user account is present - * @throws ServerException if called when not in single-user mode - */ - public static function singleUserNickname() - { - try { - $user = User::singleUser(); - return $user->nickname; - } catch (Exception $e) { - if (common_config('singleuser', 'enabled') && common_config('singleuser', 'nickname')) { - common_log(LOG_WARNING, "Warning: code attempting to pull single-user nickname when the account does not exist. If this is not setup time, this is probably a bug."); - return common_config('singleuser', 'nickname'); - } - throw $e; - } - } - - /** - * Find and shorten links in the given text using this user's URL shortening - * settings. - * - * By default, links will be left untouched if the text is shorter than the - * configured maximum notice length. Pass true for the $always parameter - * to force all links to be shortened regardless. - * - * Side effects: may save file and file_redirection records for referenced URLs. - * - * @param string $text - * @param boolean $always - * @return string - */ - public function shortenLinks($text, $always=false) - { - return common_shorten_links($text, $always, $this); - } - - /* - * Get a list of OAuth client applications that have access to this - * user's account. - */ - public function getConnectedApps($offset = 0, $limit = null) - { - $apps = new Oauth_application_user(); - - $apps->selectAdd(); - $apps->selectAdd('oauth_application_user.*'); - - $apps->joinAdd(['application_id', 'oauth_application:id']); - - $apps->profile_id = $this->getID(); - $apps->whereAdd('oauth_application_user.access_type > 0'); - - $apps->orderBy('oauth_application_user.created DESC, oauth_application.id DESC'); - - if ($offset > 0) { - $apps->limit($offset, $limit); - } - - $apps->find(); - - return $apps; - } - - /** - * 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'); - return array_diff($vars, $skip); - } - - public static function recoverPassword($nore) - { - require_once INSTALLDIR . '/lib/util/mail.php'; - - // $confirm_email will be used as a fallback if our user doesn't have a confirmed email - $confirm_email = null; - - if (common_is_email($nore)) { - $user = User::getKV('email', common_canonical_email($nore)); - - // See if it's an unconfirmed email address - if (!$user instanceof User) { - // Warning: it may actually be legit to have multiple folks - // who have claimed, but not yet confirmed, the same address. - // We'll only send to the first one that comes up. - $confirm_email = new Confirm_address(); - $confirm_email->address = common_canonical_email($nore); - $confirm_email->address_type = 'email'; - if ($confirm_email->find(true)) { - $user = User::getKV('id', $confirm_email->user_id); - } - } - - // No luck finding anyone by that email address. - if (!$user instanceof User) { - if (common_config('site', 'fakeaddressrecovery')) { - // Return without actually doing anything! We fake address recovery - // to avoid revealing which email addresses are registered with the site. - return; - } - // TRANS: Information on password recovery form if no known e-mail address was specified. - throw new ClientException(_('No user with that email address exists here.')); - } - } else { - // This might throw a NicknameException on bad nicknames - $user = User::getKV('nickname', common_canonical_nickname($nore)); - if (!$user instanceof User) { - // TRANS: Information on password recovery form if no known username was specified. - throw new ClientException(_('No user with that nickname exists here.')); - } - } - - // Try to get an unconfirmed email address if they used a user name - if (empty($user->email) && $confirm_email === null) { - $confirm_email = new Confirm_address(); - $confirm_email->user_id = $user->id; - $confirm_email->address_type = 'email'; - $confirm_email->find(); - if (!$confirm_email->fetch()) { - // Nothing found, so let's reset it to null - $confirm_email = null; - } - } - - if (empty($user->email) && !$confirm_email instanceof Confirm_address) { - // TRANS: Client error displayed on password recovery form if a user does not have a registered e-mail address. - throw new ClientException(_('No registered email address for that user.')); - } - - // Success! We have a valid user and a confirmed or unconfirmed email address - - $confirm = new Confirm_address(); - $confirm->code = common_confirmation_code(128); - $confirm->address_type = 'recover'; - $confirm->user_id = $user->id; - $confirm->address = $user->email ?: $confirm_email->address; - - if (!$confirm->insert()) { - common_log_db_error($confirm, 'INSERT', __FILE__); - // TRANS: Server error displayed if e-mail address confirmation fails in the database on the password recovery form. - throw new ServerException(_('Error saving address confirmation.')); - } - - // @todo FIXME: needs i18n. - $body = "Hey, $user->nickname."; - $body .= "\n\n"; - $body .= 'Someone just asked for a new password ' . - 'for this account on ' . common_config('site', 'name') . '.'; - $body .= "\n\n"; - $body .= 'If it was you, and you want to confirm, use the URL below:'; - $body .= "\n\n"; - $body .= "\t" . common_local_url( - 'recoverpassword', - ['code' => $confirm->code] - ); - $body .= "\n\n"; - $body .= 'If not, just ignore this message.'; - $body .= "\n\n"; - $body .= 'Thanks for your time, '; - $body .= "\n"; - $body .= common_config('site', 'name'); - $body .= "\n"; - - $headers = _mail_prepare_headers('recoverpassword', $user->nickname, $user->nickname); - // TRANS: Subject for password recovery e-mail. - mail_to_user($user, _('Password recovery requested'), $body, $headers, $confirm->address); - } - - public function streamModeOnly() - { - if (common_config('oldschool', 'enabled')) { - $osp = Old_school_prefs::getKV('user_id', $this->id); - if (!empty($osp)) { - return $osp->stream_mode_only; - } - } - - return false; - } - - public function streamNicknames() - { - if (common_config('oldschool', 'enabled')) { - $osp = Old_school_prefs::getKV('user_id', $this->id); - if (!empty($osp)) { - return $osp->stream_nicknames; - } - } - return false; - } - - public function registrationActivity() - { - $profile = $this->getProfile(); - - $service = new ActivityObject(); - - $service->type = ActivityObject::SERVICE; - $service->title = common_config('site', 'name'); - $service->link = common_root_url(); - $service->id = $service->link; - - $act = new Activity(); - - $act->actor = $profile->asActivityObject(); - $act->verb = ActivityVerb::JOIN; - - $act->objects[] = $service; - - $act->id = TagURI::mint( - 'user:register:%d', - $this->id - ); - - $act->time = strtotime($this->created); - - $act->title = _("Register"); - - $act->content = sprintf( - _('%1$s joined %2$s.'), - $profile->getBestName(), - $service->title - ); - return $act; - } - - public function isPrivateStream() - { - return $this->getProfile()->isPrivateStream(); - } - - public function hasPassword() - { - return !empty($this->password); - } - - public function setPassword($password) - { - $orig = clone($this); - $this->password = common_munge_password($password, $this->getProfile()); - - if ($this->validate() !== true) { - // TRANS: Form validation error on page where to change password. - throw new ServerException(_('Error saving user; invalid.')); - } - - if (!$this->update($orig)) { - common_log_db_error($this, 'UPDATE', __FILE__); - // TRANS: Server error displayed on page where to change password when password change - // TRANS: could not be made because of a server error. - throw new ServerException(_('Cannot save new password.')); - } - } - - public function delPref($namespace, $topic) - { - return $this->getProfile()->delPref($namespace, $topic); - } - - public function getPref($namespace, $topic, $default=null) - { - return $this->getProfile()->getPref($namespace, $topic, $default); - } - - public function getConfigPref($namespace, $topic) - { - return $this->getProfile()->getConfigPref($namespace, $topic); - } - - 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'], @@ -1107,6 +88,5 @@ class User '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 index 823af4ab1a..900f343fee 100644 --- a/src/Entity/UserGroup.php +++ b/src/Entity/UserGroup.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class UserGroup { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/UserImPrefs.php b/src/Entity/UserImPrefs.php index c5b9e6c63b..9daf5613da 100644 --- a/src/Entity/UserImPrefs.php +++ b/src/Entity/UserImPrefs.php @@ -33,9 +33,9 @@ namespace App\Entity; */ class UserImPrefs { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/UserLocationPrefs.php b/src/Entity/UserLocationPrefs.php index 03aac32d44..364ec4079e 100644 --- a/src/Entity/UserLocationPrefs.php +++ b/src/Entity/UserLocationPrefs.php @@ -33,9 +33,9 @@ namespace App\Entity; */ class UserLocationPrefs { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/UserUrlshortenerPrefs.php b/src/Entity/UserUrlshortenerPrefs.php index fb4ad15c5e..108c2d0d43 100644 --- a/src/Entity/UserUrlshortenerPrefs.php +++ b/src/Entity/UserUrlshortenerPrefs.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class UserUrlshortenerPrefs { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array { diff --git a/src/Entity/UserUsername.php b/src/Entity/UserUsername.php index 697dac955c..54e961974c 100644 --- a/src/Entity/UserUsername.php +++ b/src/Entity/UserUsername.php @@ -35,9 +35,9 @@ namespace App\Entity; */ class UserUsername { - // AUTOCODE BEGIN + // {{{ Autocode - // AUTOCODE END + // }}} Autocode public static function schemaDef(): array {