Merge remote-tracking branch 'upstream/develop' into ap-delivery-failure

This commit is contained in:
Michael 2019-03-26 21:36:46 +00:00
commit 55325f191b
122 changed files with 5068 additions and 2006 deletions

4
.gitignore vendored
View File

@ -1,6 +1,6 @@
favicon.*
.htconfig.php
.htpreconfig.php
/.htconfig.php
/.htpreconfig.php
\#*
*.log
*.out

View File

@ -1,3 +1,20 @@
Version 2019.06 (UNRELEASED) (2019-06-?)
Friendica Core:
Update to the documentation [realkinetix]
Enhancements to the API [MrPetovan]
Fixed the notification order [JeroenED]
Fixed the timezone of Friendica logs [nupplaphil]
Fixed tag completion painfully slow [AlfredSK]
Fixed a regression in notifications [MrPetovan]
Fixed an issue with smilies and code blocks [MrPetovan]
General Code cleaning and restructuring [nupplaphil]
Added frio color scheme sharing [JeroenED]
Added syslog and stream Logger [nupplaphil]
Added collapsible panel for connector permission fields [MrPetovan]
Closed Issues:
6303, 6478, 6319, 6921, 6903
Version 2019.03 (2019-03-22)
Friendica Core:
Update to the translation (CS, DE, EN-GB, EN-US, ES, FR, IT, PL, SV, ZH-CN) [translation teams]

View File

@ -1 +1 @@
2019.03
2019.06-dev

View File

@ -31,7 +31,7 @@ use Friendica\Util\DateTimeFormat;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Dalmatian Bellflower');
define('FRIENDICA_VERSION', '2019.03');
define('FRIENDICA_VERSION', '2019.06-dev');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_UPDATE_ROUTINE_VERSION', 1170);

3
config/dbstructure.config.php Normal file → Executable file
View File

@ -34,7 +34,7 @@
use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1305);
define('DB_UPDATE_VERSION', 1308);
}
return [
@ -1210,6 +1210,7 @@ return [
],
"indexes" => [
"PRIMARY" => ["tid"],
"term_type" => ["term(64)", "type"],
"oid_otype_type_term" => ["oid", "otype", "type", "term(32)"],
"uid_otype_type_term_global_created" => ["uid", "otype", "type", "term(32)", "global", "created"],
"uid_otype_type_url" => ["uid", "otype", "type", "url(64)"],

View File

@ -214,6 +214,10 @@ return [
// If activated, all hashtags will point to the local server.
'local_tags' => false,
// logger_config (String)
// Sets the logging adapter of Friendica globally (monolog, syslog, stream)
'logger_config' => 'stream',
// max_batch_queue (Integer)
// Maximum number of batched queue items for a single contact before subsequent messages are discarded.
'max_batch_queue' => 1000,

View File

@ -1112,6 +1112,7 @@ CREATE TABLE IF NOT EXISTS `term` (
`global` boolean NOT NULL DEFAULT '0' COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`tid`),
INDEX `term_type` (`term`(64), `type`),
INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`(32)),
INDEX `uid_otype_type_term_global_created` (`uid`,`otype`,`type`,`term`(32),`global`,`created`),
INDEX `uid_otype_type_url` (`uid`,`otype`,`type`,`url`(64)),

View File

@ -411,6 +411,30 @@ Hook data:
visitor => array with the contact record of the visitor
url => the query string
### jot_networks
Called when displaying the post permission screen.
Hook data is a list of form fields that need to be displayed along the ACL.
Form field array structure is:
- **type**: `checkbox` or `select`.
- **field**: Standard field data structure to be used by `field_checkbox.tpl` and `field_select.tpl`.
For `checkbox`, **field** is:
- [0] (String): Form field name; Mandatory.
- [1]: (String): Form field label; Optional, default is none.
- [2]: (Boolean): Whether the checkbox should be checked by default; Optional, default is false.
- [3]: (String): Additional help text; Optional, default is none.
- [4]: (String): Additional HTML attributes; Optional, default is none.
For `select`, **field** is:
- [0] (String): Form field name; Mandatory.
- [1] (String): Form field label; Optional, default is none.
- [2] (Boolean): Default value to be selected by default; Optional, default is none.
- [3] (String): Additional help text; Optional, default is none.
- [4] (Array): Associative array of options. Item key is option value, item value is option label; Mandatory.
## Complete list of hook callbacks
Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above.

View File

@ -35,6 +35,11 @@ Requirements
* Installation into a top-level domain or sub-domain (without a directory/path component in the URL) is preferred. Directory paths will not be as convenient to use and have not been thoroughly tested.
* If your hosting provider doesn't allow Unix shell access, you might have trouble getting everything to work.
Optional
---
* PHP ImageMagick extension (php-imagick) for animated GIF support.
Installation procedure
---

View File

@ -3,17 +3,19 @@ If you're not already logged in, do so in the frame below.
Once you've logged in (or if you are already logged in), you'll now be looking at your profile page.
This is a bit like your Facebook wall.
This is a bit like a Facebook wall.
It's where all your status messgages are kept, and where your friends come to post on your wall.
To write your status, simply click in the box that says "share".
When you do this, the box will expand.
You can see some formatting options at the top such as Bold, Italics and Underline, as well as ways to add links and pictures.
At the bottom you'll find some more links.
To write your status, simply click on the Pencil & Paper icon in the top right (in the Frio theme), or click in the box that says "share" (other themes).
When you do this, the posting dialog box will appear or the share box will expand.
You can see some formatting options such as Bold, Italics and Underline, as well as ways to add links, pictures (dependent on the theme), and a paperclip icon to attach or embed content.
You can use these to upload pictures and files from your computer, share websites with a bit of preview text, or embed video and audio files from elsewhere on the web.
With the Frio theme, the browser tab can be used to upload and post media from your account.
You can also set your post location here.
Once you've finished writing your post, click on the padlock icon to select who can see it.
If you do not use the padlock icon, your post will be public.
Once you've finished writing your post, click on the padlock icon or permissions tab to select who can see it.
If you do not change anything, your post will be public.
This means it will appear to anybody who views your profile, and in the community tab if your site has it enabled, as well as in the network tab of any of your contacts.
Play around with this a bit, then when you're ready to move on, we'll take a look at the <a href="help/Quick-Start-network">Network Tab</a>

View File

@ -11,71 +11,85 @@ Creating posts
Here you can find an overview of the different ways to create and edit your post.
One click on "Share" text box on top of your Home or Network page, and the post editor shows up:
One click on the Pencil & Paper icon in the top right of your Home or Network page, or the "Share" text box, and the post editor shows up.
Below are examples of the post editor in 3 of Friendica's common themes:
<figure>
<img src="doc/img/friendica_editor.png" alt="default editor">
<figcaption>Default post editor, with default Friendica theme (duepuntozero)</figcaption>
<img src="doc/img/editor_frio.png" alt="frio editor">
<figcaption>Post editor, with the <b>Frio</b> (popular default) theme.</figcaption>
</figure>
<p style="clear:both;"></p>
<figure>
<img src="doc/img/editor_vier.png" alt="vier editor" width="675">
<figcaption>Post editor, with the <b>Vier</b> theme.</figcaption>
</figure>
<p style="clear:both;"></p>
<figure>
<img src="doc/img/editor_dpzero.png" alt="duepuntozero editor">
<figcaption>Post editor, with the <b>Duepuntozero</b> theme.</figcaption>
</figure>
Post title is optional, you can set it clicking on "Set title".
Post title is optional, you can set it by clicking on "Set title".
Posts can optionally be in one or more categories. Write categories name separated by a comma to file your new post.
Posts can optionally be in one or more categories. Write category names separated by a comma to file your new post.
The Big Empty Textarea is where you write your new post.
You can simply enter your text there and click "Share" button, and your new post will be public on your profile page and shared to your contact.
You can simply enter your text there and click the "Share" button, and your new post will be public on your profile page and shared to your contact.
If plain text is not so exciting to you, Friendica understands BBCode to spice up your posts: bold, italic, images, links, lists..
See [BBCode tags reference](help/BBCode) page to see all what you can do.
The icons under the text area are there to help you to write posts quickly:
The icons under the text area are there to help you to write posts quickly, but vary depending on the theme:
<img src="doc/img/camera.png" width="32" height="32" alt="editor" align="left" style="padding-bottom: 20px;"> Upload a picture from your computer. The image will be uploaded and correct bbcode tag will be added to your post.*
With the Frio theme, the Underline, Italics and Bold buttons should be self-explanatory.
<img src="doc/img/camera.png" width="32" height="32" alt="editor" align="left"> Upload a picture from your computer. The image will be uploaded and correct bbcode tag will be added to your post.* In the Frio theme, use the <b>Browser</b> tab instead to Upload and/or attach content to your post.
<p style="clear:both;"></p>
<img src="doc/img/paper_clip.png" width="32" height="32" alt="paper_clip" align="left"> Add files from your computer. Same as picture, but for generic attachment to the post.*
<img src="doc/img/paper_clip.png" width="32" height="32" alt="paper_clip" align="left"> This depends on the theme: For Frio, this is to attach remote content - put in a URL to embed in your post, including video or audio content. For other themes: Add files from your computer. Same as picture, but for generic attachment to the post.*
<p style="clear:both;"></p>
<img src="doc/img/chain.png" width="32" height="32" alt="chain" align="left"> Add a web address (url). Enter an url and Friendica will add to your post a link to the url and an excerpt from the web site, if possible.
<img src="doc/img/chain.png" width="32" height="32" alt="chain" align="left"> Add a web address (url). Enter a URL and Friendica will add to your post a link to the url and an excerpt from the web site, if possible.
<p style="clear:both;"></p>
<img src="doc/img/video.png" width="32" height="32" alt="video" align="left"> Add a video. Enter the url to a video (ogg) or to a video page on youtube or vimeo, and it will be embedded in your post with a preview. Friendica is using [HTML5](http://en.wikipedia.org/wiki/HTML5_video) for embedding content. Therefore, the supported files are depending on your browser and operating system (OS). Some filetypes are WebM, MP4 and OGG.*
<img src="doc/img/video.png" width="32" height="32" alt="video" align="left"> Add a video. Enter the url to a video (ogg) or to a video page on youtube or vimeo, and it will be embedded in your post with a preview. (In the Frio theme, this is done with the paperclip as mentioned above.) Friendica is using [HTML5](http://en.wikipedia.org/wiki/HTML5_video) for embedding content. Therefore, the supported files are depending on your browser and operating system (OS). Some filetypes are WebM, MP4 and OGG.*
<p style="clear:both;"></p>
<img src="doc/img/mic.png" width="32" height="32" alt="mic" align="left" style="padding-bottom: 20px;"> Add an audio. Same as video, but for audio. Depending on your browser and operation system MP3, OGG and AAC are supported. Additionally, you are able to add URLs from audiohosters like Soundcloud.
<img src="doc/img/mic.png" width="32" height="32" alt="mic" align="left"> Add an audio. Same as video, but for audio. Depending on your browser and operation system MP3, OGG and AAC are supported. Additionally, you are able to add URLs from audiohosters like Soundcloud.
<p style="clear:both;"></p>
<img src="doc/img/globe.png" width="32" height="32" alt="globe" align="left"> Set your geographic location. This location will be added into a Google Maps search. That's why a note like "New York" or "10004" is already enough.
<img src="doc/img/globe.png" width="32" height="32" alt="globe" align="left"> <b>Or</b> <img src="doc/img/frio_location.png" width="32" height="32" alt="location" align="none"> Set your geographic location. This location will be added into a Google Maps search. That's why a note like "New York" or "10004" is already enough.
<p style="clear:both;"></p>
<br />
<p style="clear:both;"></p>
<i>* how to [upload](help/FAQ#upload) files</i>
Those icons can change with themes. Some examples:
These icons can change depending on the theme. Some examples:
<table>
<tr>
<td>Darkbubble: </td>
<td>Vier: </td>
<td><img src="doc/img/vier_icons.png" alt="vier.png" style="vertical-align:middle;"></td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Smoothly: </td>
<td><img src="doc/img/editor_darkbubble.png" alt="darkbubble.png" style="vertical-align:middle;"></td>
<td><i>(inkl. smoothly, testbubble)</i></td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Frost: </td>
<td><img src="doc/img/editor_frost.png" alt="frost.png" style="vertical-align:middle;"> </td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Vier: </td>
<td><img src="doc/img/editor_vier.png" alt="vier.png" style="vertical-align:middle;"></td>
<td><i>(inkl. dispy)</i></td>
</tr>
</table>
<p style="clear:both;">&nbsp;</p>
<i><b>*</b> how to [upload](help/FAQ#upload) files</i>
<p style="clear:both;">&nbsp;</p>
**<img src="doc/img/lock.png" width="32" height="32" alt="lock icon" style="vertical-align:middle;"> The lock**
**<img src="doc/img/lock.png" width="32" height="32" alt="lock icon" style="vertical-align:middle;"> The Lock / Permissions**
The last button, the Lock, is the most important feature in Friendica. If the lock is open, your post will be public, and will shows up on your profile page when strangers visit it.
In Frio, the Permissions tab, or in other themes, the Lock button, is the most important feature in Friendica. If the lock is open, your post will be public, and will show up on your profile page when strangers visit it.
Click on it and the *Permission settings* window (aka "*Access Control Selector*" or "*ACL Selector*") pops up. There you can select who can see the post.

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
doc/img/frio_location.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

BIN
doc/img/vier_icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -932,7 +932,6 @@ function api_format_data($root_element, $type, $data)
*/
function api_account_verify_credentials($type)
{
$a = \get_app();
if (api_user() === false) {
@ -954,13 +953,9 @@ function api_account_verify_credentials($type)
// - Adding last status
if (!$skip_status) {
$user_info["status"] = api_status_show("raw");
if (isset($user_info["status"])) {
if (!is_array($user_info["status"]) || !count($user_info["status"])) {
unset($user_info["status"]);
} else {
unset($user_info["status"]["user"]);
}
$item = api_get_last_status($user_info['pid'], $user_info['uid']);
if ($item) {
$user_info['status'] = api_format_item($item, $type);
}
}
@ -1244,105 +1239,61 @@ function api_media_upload()
api_register_func('api/media/upload', 'api_media_upload', true, API_METHOD_POST);
/**
*
* @param string $type Return type (atom, rss, xml, json)
*
* @param string $type Return format (atom, rss, xml, json)
* @param int $item_id
* @return array|string
* @throws BadRequestException
* @throws ImagickException
* @throws InternalServerErrorException
* @throws UnauthorizedException
* @return string
* @throws Exception
*/
function api_status_show($type, $item_id = 0)
function api_status_show($type, $item_id)
{
$a = \get_app();
Logger::info(API_LOG_PREFIX . 'Start', ['action' => 'status_show', 'type' => $type, 'item_id' => $item_id]);
$user_info = api_get_user($a);
$status_info = [];
Logger::log('api_status_show: user_info: '.print_r($user_info, true), Logger::DEBUG);
if (!empty($item_id)) {
// Get the item with the given id
$condition = ['id' => $item_id];
} else {
// get last public wall message
$condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(),
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
$item = api_get_item(['id' => $item_id]);
if ($item) {
$status_info = api_format_item($item, $type);
}
if ($type == "raw") {
$condition['private'] = false;
}
Logger::info(API_LOG_PREFIX . 'End', ['action' => 'get_status', 'status_info' => $status_info]);
$lastwall = Item::selectFirst(Item::ITEM_FIELDLIST, $condition, ['order' => ['id' => true]]);
return api_format_data('statuses', $type, ['status' => $status_info]);
}
if (DBA::isResult($lastwall)) {
$in_reply_to = api_in_reply_to($lastwall);
$converted = api_convert_item($lastwall);
if ($type == "xml") {
$geo = "georss:point";
} else {
$geo = "geo";
}
$status_info = [
'created_at' => api_date($lastwall['created']),
'id' => intval($lastwall['id']),
'id_str' => (string) $lastwall['id'],
'text' => $converted["text"],
'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
'truncated' => false,
'in_reply_to_status_id' => $in_reply_to['status_id'],
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
'in_reply_to_user_id' => $in_reply_to['user_id'],
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
'in_reply_to_screen_name' => $in_reply_to['screen_name'],
'user' => $user_info,
$geo => null,
'coordinates' => '',
'place' => '',
'contributors' => '',
'is_quote_status' => false,
'retweet_count' => 0,
'favorite_count' => 0,
'favorited' => $lastwall['starred'] ? true : false,
'retweeted' => false,
'possibly_sensitive' => false,
'lang' => '',
'statusnet_html' => $converted["html"],
'statusnet_conversation_id' => $lastwall['parent'],
'external_url' => System::baseUrl() . '/display/' . $lastwall['guid'],
/**
* Retrieves the last public status of the provided user info
*
* @param int $ownerId Public contact Id
* @param int $uid User Id
* @return array
* @throws Exception
*/
function api_get_last_status($ownerId, $uid)
{
$condition = [
'owner-id' => $ownerId,
'uid' => $uid,
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'private' => false
];
if (count($converted["attachments"]) > 0) {
$status_info["attachments"] = $converted["attachments"];
}
$item = api_get_item($condition);
if (count($converted["entities"]) > 0) {
$status_info["entities"] = $converted["entities"];
}
return $item;
}
if ($status_info["source"] == 'web') {
$status_info["source"] = ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']);
} elseif (ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']) != $status_info["source"]) {
$status_info["source"] = trim($status_info["source"].' ('.ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']).')');
}
/**
* Retrieves a single item record based on the provided condition and converts it for API use.
*
* @param array $condition Item table condition array
* @return array
* @throws Exception
*/
function api_get_item(array $condition)
{
$item = Item::selectFirst(Item::DISPLAY_FIELDLIST, $condition, ['order' => ['id' => true]]);
// "uid" and "self" are only needed for some internal stuff, so remove it from here
unset($status_info["user"]["uid"]);
unset($status_info["user"]["self"]);
Logger::log('status_info: '.print_r($status_info, true), Logger::DEBUG);
if ($type == "raw") {
return $status_info;
}
return api_format_data("statuses", $type, ['status' => $status_info]);
}
return $item;
}
/**
@ -1359,66 +1310,20 @@ function api_status_show($type, $item_id = 0)
*/
function api_users_show($type)
{
$a = \get_app();
$a = \Friendica\BaseObject::getApp();
$user_info = api_get_user($a);
$condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(),
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'private' => false];
$lastwall = Item::selectFirst(Item::ITEM_FIELDLIST, $condition, ['order' => ['id' => true]]);
if (DBA::isResult($lastwall)) {
$in_reply_to = api_in_reply_to($lastwall);
$converted = api_convert_item($lastwall);
if ($type == "xml") {
$geo = "georss:point";
} else {
$geo = "geo";
}
$user_info['status'] = [
'text' => $converted["text"],
'truncated' => false,
'created_at' => api_date($lastwall['created']),
'in_reply_to_status_id' => $in_reply_to['status_id'],
'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
'id' => intval($lastwall['contact-id']),
'id_str' => (string) $lastwall['contact-id'],
'in_reply_to_user_id' => $in_reply_to['user_id'],
'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
'in_reply_to_screen_name' => $in_reply_to['screen_name'],
$geo => null,
'favorited' => $lastwall['starred'] ? true : false,
'statusnet_html' => $converted["html"],
'statusnet_conversation_id' => $lastwall['parent'],
'external_url' => System::baseUrl() . "/display/" . $lastwall['guid'],
];
if (count($converted["attachments"]) > 0) {
$user_info["status"]["attachments"] = $converted["attachments"];
}
if (count($converted["entities"]) > 0) {
$user_info["status"]["entities"] = $converted["entities"];
}
if ($user_info["status"]["source"] == 'web') {
$user_info["status"]["source"] = ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']);
}
if (ContactSelector::networkToName($lastwall['network'], $user_info['url']) != $user_info["status"]["source"]) {
$user_info["status"]["source"] = trim($user_info["status"]["source"] . ' (' . ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']) . ')');
}
$item = api_get_last_status($user_info['pid'], $user_info['uid']);
if ($item) {
$user_info['status'] = api_format_item($item, $type);
}
// "uid" and "self" are only needed for some internal stuff, so remove it from here
unset($user_info["uid"]);
unset($user_info["self"]);
unset($user_info['uid']);
unset($user_info['self']);
return api_format_data("user", $type, ['user' => $user_info]);
return api_format_data('user', $type, ['user' => $user_info]);
}
/// @TODO move to top of file or somewhere better
@ -2972,7 +2877,7 @@ function api_format_items_profiles($profile_row)
/**
* @brief format items to be returned by api
*
* @param array $r array of items
* @param array $items array of items
* @param array $user_info
* @param bool $filter_user filter items by $user_info
* @param string $type Return type (atom, rss, xml, json)
@ -2982,14 +2887,13 @@ function api_format_items_profiles($profile_row)
* @throws InternalServerErrorException
* @throws UnauthorizedException
*/
function api_format_items($r, $user_info, $filter_user = false, $type = "json")
function api_format_items($items, $user_info, $filter_user = false, $type = "json")
{
$a = \get_app();
$a = \Friendica\BaseObject::getApp();
$ret = [];
foreach ((array)$r as $item) {
localize_item($item);
foreach ((array)$items as $item) {
list($status_user, $author_user, $owner_user) = api_item_get_user($a, $item);
// Look if the posts are matching if they should be filtered by user id
@ -2997,6 +2901,36 @@ function api_format_items($r, $user_info, $filter_user = false, $type = "json")
continue;
}
$status = api_format_item($item, $type, $status_user, $author_user, $owner_user);
$ret[] = $status;
}
return $ret;
}
/**
* @param array $item Item record
* @param string $type Return format (atom, rss, xml, json)
* @param array $status_user User record of the item author, can be provided by api_item_get_user()
* @param array $author_user User record of the item author, can be provided by api_item_get_user()
* @param array $owner_user User record of the item owner, can be provided by api_item_get_user()
* @return array API-formatted status
* @throws BadRequestException
* @throws ImagickException
* @throws InternalServerErrorException
* @throws UnauthorizedException
*/
function api_format_item($item, $type = "json", $status_user = null, $author_user = null, $owner_user = null)
{
$a = \Friendica\BaseObject::getApp();
if (empty($status_user) || empty($author_user) || empty($owner_user)) {
list($status_user, $author_user, $owner_user) = api_item_get_user($a, $item);
}
localize_item($item);
$in_reply_to = api_in_reply_to($item);
$converted = api_convert_item($item);
@ -3066,8 +3000,7 @@ function api_format_items($r, $user_info, $filter_user = false, $type = "json")
$retweeted_status['friendica_activities'] = api_format_items_activities($retweeted_item, $type);
$retweeted_status['created_at'] = api_date($retweeted_item['created']);
$status['retweeted_status'] = $retweeted_status;
$status['friendica_author'] = $retweeted_status['friendica_author'];
}
$status['friendica_author'] = $retweeted_status['friendica_author'];}
}
// "uid" and "self" are only needed for some internal stuff, so remove it from here
@ -3086,9 +3019,8 @@ function api_format_items($r, $user_info, $filter_user = false, $type = "json")
}
}
}
$ret[] = $status;
};
return $ret;
return $status;
}
/**
@ -5957,7 +5889,7 @@ function api_friendica_notification($type)
}
$nm = new NotificationsManager();
$notes = $nm->getAll([], "+seen -date", 50);
$notes = $nm->getAll([], ['seen' => 'ASC', 'date' => 'DESC'], 50);
if ($type == "xml") {
$xmlnotes = [];

View File

@ -923,6 +923,10 @@ function admin_page_summary(App $a)
$showwarning = true;
$warningtext[] = L10n::t('The database update failed. Please run "php bin/console.php dbstructure update" from the command line and have a look at the errors that might appear.');
}
if (Config::get('system', 'update') == Update::FAILED) {
$showwarning = true;
$warningtext[] = L10n::t('The last update failed. Please run "php bin/console.php dbstructure update" from the command line and have a look at the errors that might appear. (Some of the errors are possibly inside the logfile.)');
}
$last_worker_call = Config::get('system', 'last_worker_execution', false);
if (!$last_worker_call) {
@ -1087,7 +1091,9 @@ function admin_page_site_post(App $a)
update_table($a, "gcontact", ['connect', 'addr'], $old_host, $new_host);
// update config
Config::set('system', 'hostname', parse_url($new_url, PHP_URL_HOST));
$configFileSaver = new \Friendica\Util\Config\ConfigFileSaver($a->getBasePath());
$configFileSaver->addConfigValue('config', 'hostname', parse_url($new_url, PHP_URL_HOST));
$configFileSaver->saveToConfigFile();
Config::set('system', 'url', $new_url);
$a->setBaseURL($new_url);
@ -1105,7 +1111,6 @@ function admin_page_site_post(App $a)
// end relocate
$sitename = (!empty($_POST['sitename']) ? Strings::escapeTags(trim($_POST['sitename'])) : '');
$hostname = (!empty($_POST['hostname']) ? Strings::escapeTags(trim($_POST['hostname'])) : '');
$sender_email = (!empty($_POST['sender_email']) ? Strings::escapeTags(trim($_POST['sender_email'])) : '');
$banner = (!empty($_POST['banner']) ? trim($_POST['banner']) : false);
$shortcut_icon = (!empty($_POST['shortcut_icon']) ? Strings::escapeTags(trim($_POST['shortcut_icon'])) : '');
@ -1176,7 +1181,6 @@ function admin_page_site_post(App $a)
$itemcache_duration = (!empty($_POST['itemcache_duration']) ? intval($_POST['itemcache_duration']) : 0);
$max_comments = (!empty($_POST['max_comments']) ? intval($_POST['max_comments']) : 0);
$temppath = (!empty($_POST['temppath']) ? Strings::escapeTags(trim($_POST['temppath'])) : '');
$basepath = (!empty($_POST['basepath']) ? Strings::escapeTags(trim($_POST['basepath'])) : '');
$singleuser = (!empty($_POST['singleuser']) ? Strings::escapeTags(trim($_POST['singleuser'])) : '');
$proxy_disabled = !empty($_POST['proxy_disabled']);
$only_tag_search = !empty($_POST['only_tag_search']);
@ -1296,7 +1300,6 @@ function admin_page_site_post(App $a)
Config::set('system', 'poco_local_search' , $poco_local_search);
Config::set('system', 'nodeinfo' , $nodeinfo);
Config::set('config', 'sitename' , $sitename);
Config::set('config', 'hostname' , $hostname);
Config::set('config', 'sender_email' , $sender_email);
Config::set('system', 'suppress_tags' , $suppress_tags);
Config::set('system', 'shortcut_icon' , $shortcut_icon);
@ -1392,11 +1395,6 @@ function admin_page_site_post(App $a)
Config::set('system', 'temppath', $temppath);
if ($basepath != '') {
$basepath = BasePath::getRealPath($basepath);
}
Config::set('system', 'basepath' , $basepath);
Config::set('system', 'proxy_disabled' , $proxy_disabled);
Config::set('system', 'only_tag_search' , $only_tag_search);
@ -1536,9 +1534,6 @@ function admin_page_site(App $a)
"develop" => L10n::t("check the development version")
];
if (empty(Config::get('config', 'hostname'))) {
Config::set('config', 'hostname', $a->getHostName());
}
$diaspora_able = ($a->getURLPath() == "");
$optimize_max_tablesize = Config::get('system', 'optimize_max_tablesize', -1);
@ -1597,7 +1592,6 @@ function admin_page_site(App $a)
// name, label, value, help string, extra data...
'$sitename' => ['sitename', L10n::t("Site name"), Config::get('config', 'sitename'), ''],
'$hostname' => ['hostname', L10n::t("Host name"), Config::get('config', 'hostname'), ""],
'$sender_email' => ['sender_email', L10n::t("Sender Email"), Config::get('config', 'sender_email'), L10n::t("The email address your server shall use to send notification emails from."), "", "", "email"],
'$banner' => ['banner', L10n::t("Banner/Logo"), $banner, ""],
'$shortcut_icon' => ['shortcut_icon', L10n::t("Shortcut icon"), Config::get('system', 'shortcut_icon'), L10n::t("Link to an icon that will be used for browsers.")],
@ -1675,7 +1669,6 @@ function admin_page_site(App $a)
'$itemcache_duration' => ['itemcache_duration', L10n::t("Cache duration in seconds"), Config::get('system', 'itemcache_duration'), L10n::t("How long should the cache files be hold? Default value is 86400 seconds \x28One day\x29. To disable the item cache, set the value to -1.")],
'$max_comments' => ['max_comments', L10n::t("Maximum numbers of comments per post"), Config::get('system', 'max_comments'), L10n::t("How much comments should be shown for each post? Default value is 100.")],
'$temppath' => ['temppath', L10n::t("Temp path"), Config::get('system', 'temppath'), L10n::t("If you have a restricted system where the webserver can't access the system temp path, enter another path here.")],
'$basepath' => ['basepath', L10n::t("Base path to installation"), Config::get('system', 'basepath'), L10n::t("If the system cannot detect the correct path to your installation, enter the correct path here. This setting should only be set if you are using a restricted system and symbolic links to your webroot.")],
'$proxy_disabled' => ['proxy_disabled', L10n::t("Disable picture proxy"), Config::get('system', 'proxy_disabled'), L10n::t("The picture proxy increases performance and privacy. It shouldn't be used on systems with very low bandwidth.")],
'$only_tag_search' => ['only_tag_search', L10n::t("Only search in tags"), Config::get('system', 'only_tag_search'), L10n::t("On large systems the text search can slow down the system extremely.")],

View File

@ -1,33 +0,0 @@
<?php
/**
* @file mod/apps.php
*/
use Friendica\Content\Nav;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
function apps_content()
{
$privateaddons = Config::get('config', 'private_addons');
if ($privateaddons === "1") {
if (! local_user()) {
info(L10n::t('You must be logged in to use addons. '));
return;
};
}
$title = L10n::t('Applications');
$apps = Nav::getAppMenu();
if (count($apps) == 0) {
notice(L10n::t('No installed applications.') . EOL);
}
$tpl = Renderer::getMarkupTemplate('apps.tpl');
return Renderer::replaceMacros($tpl, [
'$title' => $title,
'$apps' => $apps,
]);
}

View File

@ -1,23 +0,0 @@
<?php
/**
* @file mod/credits.php
* Show a credits page for all the developers who helped with the project
* (only contributors to the git repositories for friendica core and the
* addons repository will be listed though ATM)
*/
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
function credits_content()
{
/* fill the page with credits */
$credits_string = file_get_contents('CREDITS.txt');
$names = explode("\n", $credits_string);
$tpl = Renderer::getMarkupTemplate('credits.tpl');
return Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Credits'),
'$thanks' => L10n::t('Friendica is a community project, that would not be possible without the help of many people. Here is a list of those who have contributed to the code or the translation of Friendica. Thank you all!'),
'$names' => $names,
]);
}

View File

@ -1,50 +0,0 @@
<?php
/**
* @file_tag_list_to_file mod/feedtest.php
*/
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Protocol\Feed;
use Friendica\Util\Network;
function feedtest_content(App $a)
{
if (!local_user()) {
info(L10n::t('You must be logged in to use this module'));
return;
};
$result = [];
if (!empty($_REQUEST['url'])) {
$url = $_REQUEST['url'];
$importer = DBA::selectFirst('user', [], ['uid' => local_user()]);
$contact_id = Contact::getIdForURL($url, local_user(), true);
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id]);
$xml = Network::fetchUrl($contact['poll']);
$dummy = null;
$import_result = Feed::import($xml, $importer, $contact, $dummy, true);
$result = [
'input' => $xml,
'output' => var_export($import_result, true),
];
}
$tpl = Renderer::getMarkupTemplate('feedtest.tpl');
$o = Renderer::replaceMacros($tpl, [
'$url' => ['url', L10n::t('Source URL'), defaults($_REQUEST, 'url', ''), ''],
'$result' => $result
]);
return $o;
}

View File

@ -1,42 +0,0 @@
<?php
/**
* @file mod/filer.php
*/
use Friendica\App;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\PConfig;
use Friendica\Core\Renderer;
use Friendica\Model\FileTag;
use Friendica\Util\XML;
function filer_content(App $a)
{
if (! local_user()) {
exit();
}
$term = XML::unescape(trim(defaults($_GET, 'term', '')));
$item_id = (($a->argc > 1) ? intval($a->argv[1]) : 0);
Logger::log('filer: tag ' . $term . ' item ' . $item_id);
if ($item_id && strlen($term)) {
// file item
FileTag::saveFile(local_user(), $item_id, $term);
} else {
// return filer dialog
$filetags = PConfig::get(local_user(), 'system', 'filetags');
$filetags = FileTag::fileToList($filetags, 'file');
$filetags = explode(",", $filetags);
$tpl = Renderer::getMarkupTemplate("filer_dialog.tpl");
$o = Renderer::replaceMacros($tpl, [
'$field' => ['term', L10n::t("Save to Folder:"), '', '', $filetags, L10n::t('- select -')],
'$submit' => L10n::t('Save'),
]);
echo $o;
}
exit();
}

View File

@ -8,12 +8,12 @@ use Detection\MobileDetect;
use DOMDocument;
use DOMXPath;
use Exception;
use Friendica\Core\Config\Cache\ConfigCacheLoader;
use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Core\Config\Configuration;
use Friendica\Database\DBA;
use Friendica\Model\Profile;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\Config\ConfigFileLoader;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
@ -75,11 +75,6 @@ class App
*/
private $mode;
/**
* @var string The App base path
*/
private $basePath;
/**
* @var string The App URL path
*/
@ -142,7 +137,7 @@ class App
*/
public function getBasePath()
{
return $this->basePath;
return $this->config->get('system', 'basepath');
}
/**
@ -165,6 +160,16 @@ class App
return $this->profiler;
}
/**
* Returns the Mode of the Application
*
* @return App\Mode The Application Mode
*/
public function getMode()
{
return $this->mode;
}
/**
* Register a stylesheet file path to be included in the <head> tag of every page.
* Inclusion is done in App->initHead().
@ -177,7 +182,7 @@ class App
*/
public function registerStylesheet($path)
{
$url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);
$url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path);
$this->stylesheets[] = trim($url, '/');
}
@ -194,7 +199,7 @@ class App
*/
public function registerFooterScript($path)
{
$url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);
$url = str_replace($this->getBasePath() . DIRECTORY_SEPARATOR, '', $path);
$this->footerScripts[] = trim($url, '/');
}
@ -206,36 +211,28 @@ class App
/**
* @brief App constructor.
*
* @param string $basePath The basedir of the app
* @param Configuration $config The Configuration
* @param App\Mode $mode The mode of this Friendica app
* @param LoggerInterface $logger The current app logger
* @param Profiler $profiler The profiler of this application
* @param bool $isBackend Whether it is used for backend or frontend (Default true=backend)
*
* @throws Exception if the Basepath is not usable
*/
public function __construct($basePath, Configuration $config, LoggerInterface $logger, Profiler $profiler, $isBackend = true)
public function __construct(Configuration $config, App\Mode $mode, LoggerInterface $logger, Profiler $profiler, $isBackend = true)
{
BaseObject::setApp($this);
$this->logger = $logger;
$this->config = $config;
$this->profiler = $profiler;
$cfgBasePath = $this->config->get('system', 'basepath');
$this->basePath = !empty($cfgBasePath) ? $cfgBasePath : $basePath;
if (!Core\System::isDirectoryUsable($this->basePath, false)) {
throw new Exception('Basepath \'' . $this->basePath . '\' isn\'t usable.');
}
$this->basePath = rtrim($this->basePath, DIRECTORY_SEPARATOR);
$this->mode = $mode;
$this->checkBackend($isBackend);
$this->checkFriendicaApp();
$this->profiler->reset();
$this->mode = new App\Mode($this->basePath);
$this->reload();
set_time_limit(0);
@ -265,9 +262,9 @@ class App
set_include_path(
get_include_path() . PATH_SEPARATOR
. $this->basePath . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
. $this->basePath . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this->basePath);
. $this->getBasePath() . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR
. $this->getBasePath() . DIRECTORY_SEPARATOR . 'library' . PATH_SEPARATOR
. $this->getBasePath());
if (!empty($_SERVER['QUERY_STRING']) && strpos($_SERVER['QUERY_STRING'], 'pagename=') === 0) {
$this->query_string = substr($_SERVER['QUERY_STRING'], 9);
@ -335,22 +332,6 @@ class App
Core\Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
}
/**
* Returns the Mode of the Application
*
* @return App\Mode The Application Mode
*
* @throws InternalServerErrorException when the mode isn't created
*/
public function getMode()
{
if (empty($this->mode)) {
throw new InternalServerErrorException('Mode of the Application is not defined');
}
return $this->mode;
}
/**
* Reloads the whole app instance
*/
@ -358,10 +339,10 @@ class App
{
$this->determineURLPath();
$this->getMode()->determine($this->basePath);
$this->getMode()->determine($this->getBasePath());
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
$loader = new ConfigCacheLoader($this->basePath);
$loader = new ConfigFileLoader($this->getBasePath(), $this->getMode());
$this->config->getCache()->load($loader->loadCoreConfig('addon'), true);
$this->profiler->update(
@ -369,6 +350,7 @@ class App
$this->config->get('rendertime', 'callstack', false));
Core\Hook::loadHooks();
$loader = new ConfigFileLoader($this->getBasePath(), $this->mode);
Core\Hook::callAll('load_config', $loader);
}
@ -470,14 +452,14 @@ class App
{
$scheme = $this->scheme;
if (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_FULL) {
if ($this->config->get('system', 'ssl_policy') == SSL_POLICY_FULL) {
$scheme = 'https';
}
// Basically, we have $ssl = true on any links which can only be seen by a logged in user
// (and also the login link). Anything seen by an outsider will have it turned off.
if (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) {
if ($this->config->get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) {
if ($ssl) {
$scheme = 'https';
} else {
@ -485,8 +467,8 @@ class App
}
}
if (Core\Config::get('config', 'hostname') != '') {
$this->hostname = Core\Config::get('config', 'hostname');
if ($this->config->get('config', 'hostname') != '') {
$this->hostname = $this->config->get('config', 'hostname');
}
return $scheme . '://' . $this->hostname . (!empty($this->getURLPath()) ? '/' . $this->getURLPath() : '' );
@ -521,12 +503,12 @@ class App
$this->urlPath = trim($parsed['path'], '\\/');
}
if (file_exists($this->basePath . '/.htpreconfig.php')) {
include $this->basePath . '/.htpreconfig.php';
if (file_exists($this->getBasePath() . '/.htpreconfig.php')) {
include $this->getBasePath() . '/.htpreconfig.php';
}
if (Core\Config::get('config', 'hostname') != '') {
$this->hostname = Core\Config::get('config', 'hostname');
if ($this->config->get('config', 'hostname') != '') {
$this->hostname = $this->config->get('config', 'hostname');
}
if (!isset($this->hostname) || ($this->hostname == '')) {
@ -537,8 +519,8 @@ class App
public function getHostName()
{
if (Core\Config::get('config', 'hostname') != '') {
$this->hostname = Core\Config::get('config', 'hostname');
if ($this->config->get('config', 'hostname') != '') {
$this->hostname = $this->config->get('config', 'hostname');
}
return $this->hostname;
@ -588,12 +570,12 @@ class App
$this->registerStylesheet($stylesheet);
$shortcut_icon = Core\Config::get('system', 'shortcut_icon');
$shortcut_icon = $this->config->get('system', 'shortcut_icon');
if ($shortcut_icon == '') {
$shortcut_icon = 'images/friendica-32.png';
}
$touch_icon = Core\Config::get('system', 'touch_icon');
$touch_icon = $this->config->get('system', 'touch_icon');
if ($touch_icon == '') {
$touch_icon = 'images/friendica-128.png';
}
@ -613,7 +595,7 @@ class App
'$update_interval' => $interval,
'$shortcut_icon' => $shortcut_icon,
'$touch_icon' => $touch_icon,
'$block_public' => intval(Core\Config::get('system', 'block_public')),
'$block_public' => intval($this->config->get('system', 'block_public')),
'$stylesheets' => $this->stylesheets,
]) . $this->page['htmlhead'];
}
@ -742,6 +724,7 @@ class App
'fetch',
'hcard',
'hostxrd',
'manifest',
'nodeinfo',
'noscrape',
'p',
@ -786,13 +769,13 @@ class App
*
if ($this->is_backend()) {
$process = 'backend';
$max_processes = Core\Config::get('system', 'max_processes_backend');
$max_processes = $this->config->get('system', 'max_processes_backend');
if (intval($max_processes) == 0) {
$max_processes = 5;
}
} else {
$process = 'frontend';
$max_processes = Core\Config::get('system', 'max_processes_frontend');
$max_processes = $this->config->get('system', 'max_processes_frontend');
if (intval($max_processes) == 0) {
$max_processes = 20;
}
@ -819,7 +802,7 @@ class App
*/
public function isMinMemoryReached()
{
$min_memory = Core\Config::get('system', 'min_memory', 0);
$min_memory = $this->config->get('system', 'min_memory', 0);
if ($min_memory == 0) {
return false;
}
@ -866,13 +849,13 @@ class App
{
if ($this->isBackend()) {
$process = 'backend';
$maxsysload = intval(Core\Config::get('system', 'maxloadavg'));
$maxsysload = intval($this->config->get('system', 'maxloadavg'));
if ($maxsysload < 1) {
$maxsysload = 50;
}
} else {
$process = 'frontend';
$maxsysload = intval(Core\Config::get('system', 'maxloadavg_frontend'));
$maxsysload = intval($this->config->get('system', 'maxloadavg_frontend'));
if ($maxsysload < 1) {
$maxsysload = 50;
}
@ -919,9 +902,9 @@ class App
}
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->basePath);
$resource = proc_open('cmd /c start /b ' . $cmdline, [], $foo, $this->getBasePath());
} else {
$resource = proc_open($cmdline . ' &', [], $foo, $this->basePath);
$resource = proc_open($cmdline . ' &', [], $foo, $this->getBasePath());
}
if (!is_resource($resource)) {
Core\Logger::log('We got no resource for command ' . $cmdline, Core\Logger::DEBUG);
@ -938,7 +921,7 @@ class App
*/
public function getSenderEmailAddress()
{
$sender_email = Core\Config::get('config', 'sender_email');
$sender_email = $this->config->get('config', 'sender_email');
if (empty($sender_email)) {
$hostname = $this->getHostName();
if (strpos($hostname, ':')) {
@ -982,7 +965,7 @@ class App
*/
private function computeCurrentTheme()
{
$system_theme = Core\Config::get('system', 'theme');
$system_theme = $this->config->get('system', 'theme');
if (!$system_theme) {
throw new Exception(Core\L10n::t('No system theme config value set.'));
}
@ -990,7 +973,7 @@ class App
// Sane default
$this->currentTheme = $system_theme;
$allowed_themes = explode(',', Core\Config::get('system', 'allowed_themes', $system_theme));
$allowed_themes = explode(',', $this->config->get('system', 'allowed_themes', $system_theme));
$page_theme = null;
// Find the theme that belongs to the user whose stuff we are looking at
@ -1007,7 +990,7 @@ class App
// Specific mobile theme override
if (($this->is_mobile || $this->is_tablet) && Core\Session::get('show-mobile', true)) {
$system_mobile_theme = Core\Config::get('system', 'mobile-theme');
$system_mobile_theme = $this->config->get('system', 'mobile-theme');
$user_mobile_theme = Core\Session::get('mobile-theme', $system_mobile_theme);
// --- means same mobile theme as desktop
@ -1078,7 +1061,7 @@ class App
*/
public function checkURL()
{
$url = Core\Config::get('system', 'url');
$url = $this->config->get('system', 'url');
// if the url isn't set or the stored url is radically different
// than the currently visited url, store the current value accordingly.
@ -1087,7 +1070,7 @@ class App
// We will only change the url to an ip address if there is no existing setting
if (empty($url) || (!Util\Strings::compareLink($url, $this->getBaseURL())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $this->getHostName()))) {
Core\Config::set('system', 'url', $this->getBaseURL());
$this->config->set('system', 'url', $this->getBaseURL());
}
}
@ -1120,8 +1103,8 @@ class App
if (!$this->getMode()->isInstall()) {
// Force SSL redirection
if (Core\Config::get('system', 'force_ssl') && ($this->getScheme() == "http")
&& intval(Core\Config::get('system', 'ssl_policy')) == SSL_POLICY_FULL
if ($this->config->get('system', 'force_ssl') && ($this->getScheme() == "http")
&& intval($this->config->get('system', 'ssl_policy')) == SSL_POLICY_FULL
&& strpos($this->getBaseURL(), 'https://') === 0
&& $_SERVER['REQUEST_METHOD'] == 'GET') {
header('HTTP/1.1 302 Moved Temporarily');
@ -1204,7 +1187,7 @@ class App
$this->module = 'maintenance';
} else {
$this->checkURL();
Core\Update::check($this->basePath, false);
Core\Update::check($this->getBasePath(), false);
Core\Addon::loadAddons();
Core\Hook::loadHooks();
}
@ -1261,7 +1244,7 @@ class App
$this->module = "login";
}
$privateapps = Core\Config::get('config', 'private_addons', false);
$privateapps = $this->config->get('config', 'private_addons', false);
if (Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) {
//Check if module is an app and if public access to apps is allowed or not
if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) {
@ -1446,7 +1429,7 @@ class App
header("X-Friendica-Version: " . FRIENDICA_VERSION);
header("Content-type: text/html; charset=utf-8");
if (Core\Config::get('system', 'hsts') && (Core\Config::get('system', 'ssl_policy') == SSL_POLICY_FULL)) {
if ($this->config->get('system', 'hsts') && ($this->config->get('system', 'ssl_policy') == SSL_POLICY_FULL)) {
header("Strict-Transport-Security: max-age=31536000");
}

View File

@ -196,7 +196,7 @@ class ForumManager
*/
public static function countUnseenItems()
{
$r = q(
$stmtContacts = DBA::p(
"SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item`
INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id`
WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen`
@ -208,6 +208,6 @@ class ForumManager
intval(local_user())
);
return $r;
return DBA::toArray($stmtContacts);
}
}

View File

@ -213,7 +213,6 @@ class Smilies
return $text;
}
$text = preg_replace_callback('/<pre>(.*?)<\/pre>/ism' , 'self::encode', $text);
$text = preg_replace_callback('/<code>(.*?)<\/code>/ism', 'self::encode', $text);
if ($no_images) {
@ -231,7 +230,6 @@ class Smilies
$text = preg_replace_callback('/&lt;(3+)/', 'self::pregHeart', $text);
$text = self::strOrigReplace($smilies['texts'], $smilies['icons'], $text);
$text = preg_replace_callback('/<pre>(.*?)<\/pre>/ism', 'self::decode', $text);
$text = preg_replace_callback('/<code>(.*?)<\/code>/ism', 'self::decode', $text);
return $text;
@ -244,7 +242,7 @@ class Smilies
*/
private static function encode($m)
{
return(str_replace($m[1], Strings::base64UrlEncode($m[1]), $m[0]));
return '<code>' . Strings::base64UrlEncode($m[1]) . '</code>';
}
/**
@ -255,7 +253,7 @@ class Smilies
*/
private static function decode($m)
{
return(str_replace($m[1], Strings::base64UrlDecode($m[1]), $m[0]));
return '<code>' . Strings::base64UrlDecode($m[1]) . '</code>';
}

View File

@ -266,14 +266,12 @@ class ACL extends BaseObject
$default_permissions = self::getDefaultUserPermissions($user);
}
$jotnets = '';
$jotnets_fields = [];
if ($show_jotnets) {
$imap_disabled = !function_exists('imap_open') || Config::get('system', 'imap_disabled');
$mail_enabled = false;
$pubmail_enabled = false;
if (!$imap_disabled) {
if (function_exists('imap_open') && !Config::get('system', 'imap_disabled')) {
$mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', local_user()]);
if (DBA::isResult($mailacct)) {
$mail_enabled = true;
@ -283,14 +281,17 @@ class ACL extends BaseObject
if (empty($default_permissions['hidewall'])) {
if ($mail_enabled) {
$selected = $pubmail_enabled ? ' checked="checked"' : '';
$jotnets .= '<div class="profile-jot-net"><input type="checkbox" name="pubmail_enable"' . $selected . ' value="1" /> ' . L10n::t("Post to Email") . '</div>';
$jotnets_fields[] = [
'type' => 'checkbox',
'field' => [
'pubmail_enable',
L10n::t('Post to Email'),
$pubmail_enabled
]
];
}
Hook::callAll('jot_networks', $jotnets);
} else {
$jotnets .= L10n::t('Connectors disabled, since "%s" is enabled.',
L10n::t('Hide your profile details from unknown viewers?'));
Hook::callAll('jot_networks', $jotnets_fields);
}
}
@ -306,7 +307,10 @@ class ACL extends BaseObject
'$networks' => $show_jotnets,
'$emailcc' => L10n::t('CC: email addresses'),
'$emtitle' => L10n::t('Example: bob@example.com, mary@example.com'),
'$jotnets' => $jotnets,
'$jotnets_enabled' => empty($default_permissions['hidewall']),
'$jotnets_summary' => L10n::t('Connectors'),
'$jotnets_fields' => $jotnets_fields,
'$jotnets_disabled_label' => L10n::t('Connectors disabled, since "%s" is enabled.', L10n::t('Hide your profile details from unknown viewers?')),
'$aclModalTitle' => L10n::t('Permissions'),
'$aclModalDismiss' => L10n::t('Close'),
'$features' => [

View File

@ -4,7 +4,7 @@
*/
namespace Friendica\Core;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
/**
* @brief Class for storing data for a short time

View File

@ -5,7 +5,7 @@ namespace Friendica\Core\Config\Cache;
/**
* The Friendica config cache for the application
* Initial, all *.config.php files are loaded into this cache with the
* ConfigCacheLoader ( @see ConfigCacheLoader )
* ConfigFileLoader ( @see ConfigFileLoader )
*/
class ConfigCache implements IConfigCache, IPConfigCache
{

View File

@ -10,6 +10,16 @@ namespace Friendica\Core\Config;
*/
class Configuration
{
/**
* The blacklist of configuration settings, which should not get saved to the backend
* @var array
*/
private $configSaveBlacklist = [
'config' => [
'hostname' => true,
]
];
/**
* @var Cache\IConfigCache
*/
@ -117,7 +127,7 @@ class Configuration
$cached = $this->configCache->set($cat, $key, $value);
// If there is no connected adapter, we're finished
if (!$this->configAdapter->isConnected()) {
if (!$this->configAdapter->isConnected() || !empty($this->configSaveBlacklist[$cat][$key])) {
return $cached;
}

View File

@ -7,6 +7,7 @@ use Friendica\BaseObject;
use Friendica\Core\Config;
use Friendica\Core\Installer;
use Friendica\Core\Theme;
use Friendica\Util\Config\ConfigFileLoader;
use RuntimeException;
class AutomaticInstallation extends Console
@ -74,6 +75,8 @@ HELP;
$installer = new Installer();
$configCache = $a->getConfigCache();
$this->out(" Complete!\n\n");
// Check Environment
@ -81,7 +84,7 @@ HELP;
$installer->resetChecks();
if (!$this->runBasicChecks($installer)) {
if (!$this->runBasicChecks($installer, $configCache)) {
$errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage);
}
@ -100,41 +103,51 @@ HELP;
}
}
$db_host = $a->getConfigCache()->get('database', 'hostname');
$db_user = $a->getConfigCache()->get('database', 'username');
$db_pass = $a->getConfigCache()->get('database', 'password');
$db_data = $a->getConfigCache()->get('database', 'database');
//reload the config cache
$loader = new ConfigFileLoader($a->getBasePath(), $a->getMode());
$loader->setupCache($configCache);
} else {
// Creating config file
$this->out("Creating config file...\n");
$save_db = $this->getOption(['s', 'savedb'], false);
$db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? getenv('MYSQL_HOST') : '');
//$db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? (getenv('MYSQL_HOST') ? getenv('MYSQL_HOST') : Installer::DEFAULT_HOST) : '');
$db_host = $this->getOption(['H', 'dbhost'], ($save_db) ? (getenv('MYSQL_HOST')) : Installer::DEFAULT_HOST);
$db_port = $this->getOption(['p', 'dbport'], ($save_db) ? getenv('MYSQL_PORT') : null);
$db_data = $this->getOption(['d', 'dbdata'], ($save_db) ? getenv('MYSQL_DATABASE') : '');
$db_user = $this->getOption(['U', 'dbuser'], ($save_db) ? getenv('MYSQL_USER') . getenv('MYSQL_USERNAME') : '');
$db_pass = $this->getOption(['P', 'dbpass'], ($save_db) ? getenv('MYSQL_PASSWORD') : '');
$url_path = $this->getOption(['u', 'urlpath'], !empty('FRIENDICA_URL_PATH') ? getenv('FRIENDICA_URL_PATH') : null);
$configCache->set('database', 'hostname', $db_host . (!empty($db_port) ? ':' . $db_port : ''));
$configCache->set('database', 'database',
$this->getOption(['d', 'dbdata'],
($save_db) ? getenv('MYSQL_DATABASE') : ''));
$configCache->set('database', 'username',
$this->getOption(['U', 'dbuser'],
($save_db) ? getenv('MYSQL_USER') . getenv('MYSQL_USERNAME') : ''));
$configCache->set('database', 'password',
$this->getOption(['P', 'dbpass'],
($save_db) ? getenv('MYSQL_PASSWORD') : ''));
$php_path = $this->getOption(['b', 'phppath'], !empty('FRIENDICA_PHP_PATH') ? getenv('FRIENDICA_PHP_PATH') : null);
$admin_mail = $this->getOption(['A', 'admin'], !empty('FRIENDICA_ADMIN_MAIL') ? getenv('FRIENDICA_ADMIN_MAIL') : '');
$tz = $this->getOption(['T', 'tz'], !empty('FRIENDICA_TZ') ? getenv('FRIENDICA_TZ') : '');
$lang = $this->getOption(['L', 'lang'], !empty('FRIENDICA_LANG') ? getenv('FRIENDICA_LANG') : '');
if (!empty($php_path)) {
$configCache->set('config', 'php_path', $php_path);
}
$configCache->set('config', 'admin_email',
$this->getOption(['A', 'admin'],
!empty(getenv('FRIENDICA_ADMIN_MAIL')) ? getenv('FRIENDICA_ADMIN_MAIL') : ''));
$configCache->set('system', 'default_timezone',
$this->getOption(['T', 'tz'],
!empty(getenv('FRIENDICA_TZ')) ? getenv('FRIENDICA_TZ') : Installer::DEFAULT_TZ));
$configCache->set('system', 'language',
$this->getOption(['L', 'lang'],
!empty(getenv('FRIENDICA_LANG')) ? getenv('FRIENDICA_LANG') : Installer::DEFAULT_LANG));
if (empty($php_path)) {
$php_path = $installer->getPHPPath();
$configCache->set('config', 'php_path', $installer->getPHPPath());
}
$installer->createConfig(
$php_path,
$url_path,
(!empty($db_port) ? $db_host . ':' . $db_port : $db_host),
$db_user,
$db_pass,
$db_data,
$tz,
$lang,
$admin_mail,
$a,
$configCache,
$a->getBasePath()
);
}
@ -146,7 +159,7 @@ HELP;
$installer->resetChecks();
if (!$installer->checkDB($a->getBasePath(), $a->getConfigCache(), $a->getProfiler(), $db_host, $db_user, $db_pass, $db_data)) {
if (!$installer->checkDB($a->getBasePath(), $configCache, $a->getProfiler())) {
$errorMessage = $this->extractErrors($installer->getChecks());
throw new RuntimeException($errorMessage);
}
@ -180,12 +193,13 @@ HELP;
}
/**
* @param Installer $installer the Installer instance
* @param Installer $installer The Installer instance
* @param Config\Cache\IConfigCache $configCache The config cache
*
* @return bool true if checks were successfully, otherwise false
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function runBasicChecks(Installer $installer)
private function runBasicChecks(Installer $installer, Config\Cache\IConfigCache $configCache)
{
$checked = true;
@ -207,8 +221,8 @@ HELP;
}
$php_path = null;
if (!empty(Config::get('config', 'php_path'))) {
$php_path = Config::get('config', 'php_path');
if ($configCache->has('config', 'php_path')) {
$php_path = $configCache->get('config', 'php_path');
}
if (!$installer->checkPHP($php_path, true)) {

View File

@ -6,6 +6,7 @@ namespace Friendica\Core;
use DOMDocument;
use Exception;
use Friendica\App;
use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
@ -129,33 +130,28 @@ class Installer
* - Creates `config/local.config.php`
* - Installs Database Structure
*
* @param string $phppath Path to the PHP-Binary (optional, if not set e.g. 'php' or '/usr/bin/php')
* @param string $urlpath Path based on the URL of Friendica (e.g. '/friendica')
* @param string $dbhost Hostname/IP of the Friendica Database
* @param string $dbuser Username of the Database connection credentials
* @param string $dbpass Password of the Database connection credentials
* @param string $dbdata Name of the Database
* @param string $timezone Timezone of the Friendica Installaton (e.g. 'Europe/Berlin')
* @param string $language 2-letter ISO 639-1 code (eg. 'en')
* @param string $adminmail Mail-Adress of the administrator
* @param App $app The Friendica App
* @param IConfigCache $configCache The config cache with all config relevant information
* @param string $basepath The basepath of Friendica
*
* @return bool true if the config was created, otherwise false
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function createConfig($phppath, $urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $timezone, $language, $adminmail, $basepath)
public function createConfig(App $app, IConfigCache $configCache, $basepath)
{
$tpl = Renderer::getMarkupTemplate('local.config.tpl');
$txt = Renderer::replaceMacros($tpl, [
'$phpath' => $phppath,
'$dbhost' => $dbhost,
'$dbuser' => $dbuser,
'$dbpass' => $dbpass,
'$dbdata' => $dbdata,
'$timezone' => $timezone,
'$language' => $language,
'$urlpath' => $urlpath,
'$adminmail' => $adminmail,
'$dbhost' => $configCache->get('database', 'hostname'),
'$dbuser' => $configCache->get('database', 'username'),
'$dbpass' => $configCache->get('database', 'password'),
'$dbdata' => $configCache->get('database', 'database'),
'$phpath' => $this->getPHPPath(),
'$adminmail' => $configCache->get('config', 'admin_email'),
'$timezone' => $configCache->get('system', 'default_timezone'),
'$language' => $configCache->get('system', 'language'),
'$urlpath' => $app->getURLPath(),
]);
$result = file_put_contents($basepath . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php', $txt);
@ -594,16 +590,17 @@ class Installer
* @param string $basePath The basepath of this call
* @param IConfigCache $configCache The configuration cache
* @param Profiler $profiler The profiler of this app
* @param string $dbhost Hostname/IP of the Friendica Database
* @param string $dbuser Username of the Database connection credentials
* @param string $dbpass Password of the Database connection credentials
* @param string $dbdata Name of the Database
*
* @return bool true if the check was successful, otherwise false
* @throws Exception
*/
public function checkDB($basePath, IConfigCache $configCache, Profiler $profiler, $dbhost, $dbuser, $dbpass, $dbdata)
public function checkDB($basePath, IConfigCache $configCache, Profiler $profiler)
{
$dbhost = $configCache->get('database', 'hostname');
$dbuser = $configCache->get('database', 'username');
$dbpass = $configCache->get('database', 'password');
$dbdata = $configCache->get('database', 'database');
if (!DBA::connect($basePath, $configCache, $profiler, $dbhost, $dbuser, $dbpass, $dbdata)) {
$this->addCheck(L10n::t('Could not connect to database.'), false, true, '');

View File

@ -7,7 +7,7 @@
namespace Friendica\Core;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
use Friendica\Core\Cache\IMemoryCacheDriver;
/**

View File

@ -4,14 +4,13 @@
*/
namespace Friendica\Core;
use Friendica\BaseObject;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* @brief Logger functions
*/
class Logger extends BaseObject
class Logger
{
/**
* @see Logger::error()
@ -96,13 +95,7 @@ class Logger extends BaseObject
*/
public static function emergency($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->emergency($message, $context);
self::getApp()->GetProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -120,13 +113,7 @@ class Logger extends BaseObject
*/
public static function alert($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->alert($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -143,13 +130,7 @@ class Logger extends BaseObject
*/
public static function critical($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->critical($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -165,14 +146,7 @@ class Logger extends BaseObject
*/
public static function error($message, $context = [])
{
if (!isset(self::$logger)) {
echo "not set!?\n";
return;
}
$stamp1 = microtime(true);
self::$logger->error($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -190,13 +164,7 @@ class Logger extends BaseObject
*/
public static function warning($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->warning($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -211,13 +179,7 @@ class Logger extends BaseObject
*/
public static function notice($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->notice($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -234,13 +196,7 @@ class Logger extends BaseObject
*/
public static function info($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->info($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -255,13 +211,7 @@ class Logger extends BaseObject
*/
public static function debug($message, $context = [])
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->debug($message, $context);
self::getApp()->getProfiler()->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
@ -275,13 +225,7 @@ class Logger extends BaseObject
*/
public static function log($msg, $level = LogLevel::INFO)
{
if (!isset(self::$logger)) {
return;
}
$stamp1 = microtime(true);
self::$logger->log($level, $msg);
self::getApp()->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
}
/**
@ -296,12 +240,10 @@ class Logger extends BaseObject
*/
public static function devLog($msg, $level = LogLevel::DEBUG)
{
if (!isset(self::$logger)) {
if (!isset(self::$devLogger)) {
return;
}
$stamp1 = microtime(true);
self::$devLogger->log($level, $msg);
self::getApp()->getProfiler()->saveTimestamp($stamp1, "file", System::callstack());
}
}

View File

@ -36,7 +36,7 @@ class NotificationsManager extends BaseObject
* - msg_plain: message as plain text string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function _set_extra($notes)
private function _set_extra(array $notes)
{
$rets = [];
foreach ($notes as $n) {
@ -55,50 +55,28 @@ class NotificationsManager extends BaseObject
* @brief Get all notifications for local_user()
*
* @param array $filter optional Array "column name"=>value: filter query by columns values
* @param string $order optional Space separated list of column to sort by.
* Prepend name with "+" to sort ASC, "-" to sort DESC. Default to "-date"
* @param array $order optional Array to order by
* @param string $limit optional Query limits
*
* @return array of results or false on errors
* @return array|bool of results or false on errors
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function getAll($filter = [], $order = "-date", $limit = "")
public function getAll($filter = [], $order = ['date' => 'DESC'], $limit = "")
{
$filter_str = [];
$filter_sql = "";
foreach ($filter as $column => $value) {
$filter_str[] = sprintf("`%s` = '%s'", $column, DBA::escape($value));
}
if (count($filter_str) > 0) {
$filter_sql = "AND " . implode(" AND ", $filter_str);
$params = [];
$params['order'] = $order;
if (!empty($limit)) {
$params['limit'] = $limit;
}
$aOrder = explode(" ", $order);
$asOrder = [];
foreach ($aOrder as $o) {
$dir = "asc";
if ($o[0] === "-") {
$dir = "desc";
$o = substr($o, 1);
}
if ($o[0] === "+") {
$dir = "asc";
$o = substr($o, 1);
}
$asOrder[] = "$o $dir";
}
$order_sql = implode(", ", $asOrder);
$dbFilter = array_merge($filter, ['uid' => local_user()]);
if ($limit != "") {
$limit = " LIMIT " . $limit;
}
$r = q(
"SELECT * FROM `notify` WHERE `uid` = %d $filter_sql ORDER BY $order_sql $limit",
intval(local_user())
);
$stmtNotifies = DBA::select('notify', [], $dbFilter, $params);
if (DBA::isResult($r)) {
return $this->_set_extra($r);
if (DBA::isResult($stmtNotifies)) {
return $this->_set_extra(DBA::toArray($stmtNotifies));
}
return false;
@ -113,13 +91,9 @@ class NotificationsManager extends BaseObject
*/
public function getByID($id)
{
$r = q(
"SELECT * FROM `notify` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($id),
intval(local_user())
);
if (DBA::isResult($r)) {
return $this->_set_extra($r)[0];
$stmtNotify = DBA::selectFirst('notify', [], ['id' => $id, 'uid' => local_user()]);
if (DBA::isResult($stmtNotify)) {
return $this->_set_extra([$stmtNotify])[0];
}
return null;
}
@ -134,14 +108,13 @@ class NotificationsManager extends BaseObject
*/
public function setSeen($note, $seen = true)
{
return q(
"UPDATE `notify` SET `seen` = %d WHERE (`link` = '%s' OR (`parent` != 0 AND `parent` = %d AND `otype` = '%s')) AND `uid` = %d",
intval($seen),
DBA::escape($note['link']),
intval($note['parent']),
DBA::escape($note['otype']),
intval(local_user())
);
return DBA::update('notify', ['seen' => $seen], [
'(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?',
$note['link'],
$note['parent'],
$note['otype'],
local_user()
]);
}
/**
@ -153,11 +126,7 @@ class NotificationsManager extends BaseObject
*/
public function setAllSeen($seen = true)
{
return q(
"UPDATE `notify` SET `seen` = %d WHERE `uid` = %d",
intval($seen),
intval(local_user())
);
return DBA::update('notify', ['seen' => $seen], ['uid' => local_user()]);
}
/**
@ -460,20 +429,22 @@ class NotificationsManager extends BaseObject
$notifs = [];
$sql_seen = "";
$filter = ['uid' => local_user()];
if ($seen === 0) {
$sql_seen = " AND NOT `seen` ";
$filter['seen'] = false;
}
$r = q(
"SELECT `id`, `url`, `photo`, `msg`, `date`, `seen`, `verb` FROM `notify`
WHERE `uid` = %d $sql_seen ORDER BY `date` DESC LIMIT %d, %d ",
intval(local_user()),
intval($start),
intval($limit)
);
$params = [];
$params['order'] = ['date' => 'DESC'];
$params['limit'] = [$start, $limit];
if (DBA::isResult($r)) {
$notifs = $this->formatNotifs($r, $ident);
$stmtNotifies = DBA::select('notify',
['id', 'url', 'photo', 'msg', 'date', 'seen', 'verb'],
$filter,
$params);
if (DBA::isResult($stmtNotifies)) {
$notifs = $this->formatNotifs(DBA::toArray($stmtNotifies), $ident);
}
$arr = [
@ -596,7 +567,7 @@ class NotificationsManager extends BaseObject
}
/// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact
$r = q(
$stmtNotifies = DBA::p(
"SELECT `intro`.`id` AS `intro_id`, `intro`.*, `contact`.*,
`fcontact`.`name` AS `fname`, `fcontact`.`url` AS `furl`, `fcontact`.`addr` AS `faddr`,
`fcontact`.`photo` AS `fphoto`, `fcontact`.`request` AS `frequest`,
@ -613,8 +584,8 @@ class NotificationsManager extends BaseObject
intval($start),
intval($limit)
);
if (DBA::isResult($r)) {
$notifs = $this->formatIntros($r);
if (DBA::isResult($stmtNotifies)) {
$notifs = $this->formatIntros(DBA::toArray($stmtNotifies));
}
$arr = [

4
src/Core/README.md Normal file
View File

@ -0,0 +1,4 @@
## Friendica\Core
The Core namespace contains classes, which are essential to Friendica.

View File

@ -2,8 +2,13 @@
namespace Friendica\Core;
use Friendica\App;
use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Util\BasePath;
use Friendica\Util\Config\ConfigFileLoader;
use Friendica\Util\Config\ConfigFileSaver;
use Friendica\Util\Strings;
class Update
@ -24,6 +29,11 @@ class Update
return;
}
// Don't check the status if the last update was failed
if (Config::get('system', 'update', Update::SUCCESS, true) == Update::FAILED) {
return;
}
$build = Config::get('system', 'build');
if (empty($build)) {
@ -101,7 +111,9 @@ class Update
for ($x = $stored + 1; $x <= $current; $x++) {
$r = self::runUpdateFunction($x, 'pre_update');
if (!$r) {
break;
Config::set('system', 'update', Update::FAILED);
Lock::release('dbupdate');
return $r;
}
}
@ -115,6 +127,7 @@ class Update
);
}
Logger::error('Update ERROR.', ['from' => $stored, 'to' => $current, 'retval' => $retval]);
Config::set('system', 'update', Update::FAILED);
Lock::release('dbupdate');
return $retval;
} else {
@ -127,7 +140,9 @@ class Update
for ($x = $stored + 1; $x <= $current; $x++) {
$r = self::runUpdateFunction($x, 'update');
if (!$r) {
break;
Config::set('system', 'update', Update::FAILED);
Lock::release('dbupdate');
return $r;
}
}
@ -136,6 +151,7 @@ class Update
self::updateSuccessfull($stored, $current);
}
Config::set('system', 'update', Update::SUCCESS);
Lock::release('dbupdate');
}
}
@ -208,6 +224,93 @@ class Update
}
}
/**
* Checks the config settings and saves given config values into the config file
*
* @param string $basePath The basepath of Friendica
* @param App\Mode $mode The Application mode
*
* @return bool True, if something has been saved
*/
public static function saveConfigToFile($basePath, App\Mode $mode)
{
$configFileLoader = new ConfigFileLoader($basePath, $mode);
$configCache = new Config\Cache\ConfigCache();
$configFileLoader->setupCache($configCache, true);
$configFileSaver = new ConfigFileSaver($basePath);
$updated = false;
if (self::updateConfigEntry($configCache, $configFileSaver,'config', 'hostname')) {
$updated = true;
};
if (self::updateConfigEntry($configCache, $configFileSaver,'system', 'basepath', BasePath::create(dirname(__DIR__) . '/../'))) {
$updated = true;
}
// In case there is nothing to do, skip the update
if (!$updated) {
return true;
}
if (!$configFileSaver->saveToConfigFile()) {
Logger::alert('Config entry update failed - maybe wrong permission?');
return false;
}
DBA::delete('config', ['cat' => 'config', 'k' => 'hostname']);
DBA::delete('config', ['cat' => 'system', 'k' => 'basepath']);
return true;
}
/**
* Adds a value to the ConfigFileSave in case it isn't already updated
*
* @param IConfigCache $configCache The cached config file
* @param ConfigFileSaver $configFileSaver The config file saver
* @param string $cat The config category
* @param string $key The config key
* @param string $default A default value, if none of the settings are valid
*
* @return boolean True, if a value was updated
*
* @throws \Exception if DBA or Logger doesn't work
*/
private static function updateConfigEntry(IConfigCache $configCache, ConfigFileSaver $configFileSaver, $cat, $key, $default = '')
{
// check if the config file differs from the whole configuration (= The db contains other values)
$fileConfig = $configCache->get($cat, $key);
$savedConfig = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]);
if (DBA::isResult($savedConfig)) {
$savedValue = $savedConfig['v'];
} else {
$savedValue = null;
}
// If the db contains a config value, check it
if (isset($savedValue) && $fileConfig !== $savedValue) {
Logger::info('Difference in config found', ['cat' => $cat, 'key' => $key, 'file' => $fileConfig, 'saved' => $savedValue]);
$configFileSaver->addConfigValue($cat, $key, $savedValue);
return true;
// If both config values are not set, use the default value
} elseif (!isset($fileConfig) && !isset($savedValue)) {
Logger::info('Using default for config', ['cat' => $cat, 'key' => $key, 'value' => $default]);
$configFileSaver->addConfigValue($cat, $key, $default);
return true;
// If either the file config value isn't empty or the db value is the same as the
// file config value, skip it
} else {
Logger::info('No Difference in config found', ['cat' => $cat, 'key' => $key, 'value' => $fileConfig, 'saved' => $savedValue]);
return false;
}
}
/**
* send the email and do what is needed to do on update fails
*

View File

@ -6,6 +6,7 @@ namespace Friendica\Core;
use Friendica\App;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Photo;
use Friendica\Object\Image;
use Friendica\Util\Strings;
@ -35,12 +36,11 @@ class UserImport
*/
private static function checkCols($table, &$arr)
{
$query = sprintf("SHOW COLUMNS IN `%s`", DBA::escape($table));
Logger::log("uimport: $query", Logger::DEBUG);
$r = q($query);
$tableColumns = DBStructure::getColumns($table);
$tcols = [];
// get a plain array of column names
foreach ($r as $tcol) {
foreach ($tableColumns as $tcol) {
$tcols[] = $tcol['Field'];
}
// remove inexistent columns
@ -66,16 +66,12 @@ class UserImport
}
self::checkCols($table, $arr);
$cols = implode("`,`", array_map(['Friendica\Database\DBA', 'escape'], array_keys($arr)));
$vals = implode("','", array_map(['Friendica\Database\DBA', 'escape'], array_values($arr)));
$query = "INSERT INTO `$table` (`$cols`) VALUES ('$vals')";
Logger::log("uimport: $query", Logger::TRACE);
if (self::IMPORT_DEBUG) {
return true;
}
return q($query);
return DBA::insert($table, $arr);
}
/**

View File

@ -844,4 +844,18 @@ class DBStructure
return $retval;
}
/**
* Returns the columns of a table
*
* @param string $table Table name
*
* @return array An array of the table columns
* @throws Exception
*/
public static function getColumns($table)
{
$stmtColumns = DBA::p("SHOW COLUMNS FROM `" . $table . "`");
return DBA::toArray($stmtColumns);
}
}

View File

@ -1,8 +1,10 @@
<?php
namespace Friendica\Core\Cache;
namespace Friendica\Factory;
use Friendica\Core\Cache\ICacheDriver;
use Friendica\Core\Config;
use Friendica\Core\Cache;
/**
* Class CacheDriverFactory
@ -27,22 +29,22 @@ class CacheDriverFactory
$memcache_host = Config::get('system', 'memcache_host');
$memcache_port = Config::get('system', 'memcache_port');
return new MemcacheCacheDriver($memcache_host, $memcache_port);
return new Cache\MemcacheCacheDriver($memcache_host, $memcache_port);
break;
case 'memcached':
$memcached_hosts = Config::get('system', 'memcached_hosts');
return new MemcachedCacheDriver($memcached_hosts);
return new Cache\MemcachedCacheDriver($memcached_hosts);
break;
case 'redis':
$redis_host = Config::get('system', 'redis_host');
$redis_port = Config::get('system', 'redis_port');
return new RedisCacheDriver($redis_host, $redis_port);
return new Cache\RedisCacheDriver($redis_host, $redis_port);
break;
default:
return new DatabaseCacheDriver();
return new Cache\DatabaseCacheDriver();
}
}
}

View File

@ -6,18 +6,19 @@ use Friendica\Core;
use Friendica\Core\Config;
use Friendica\Core\Config\Adapter;
use Friendica\Core\Config\Cache;
use Friendica\Util\Config\ConfigFileLoader;
class ConfigFactory
{
/**
* @param Cache\ConfigCacheLoader $loader The Config Cache loader (INI/config/.htconfig)
* @param ConfigFileLoader $loader The Config Cache loader (INI/config/.htconfig)
*
* @return Cache\ConfigCache
*/
public static function createCache(Cache\ConfigCacheLoader $loader)
public static function createCache(ConfigFileLoader $loader)
{
$configCache = new Cache\ConfigCache();
$loader->loadConfigFiles($configCache);
$loader->setupCache($configCache);
return $configCache;
}

View File

@ -3,9 +3,9 @@
namespace Friendica\Factory;
use Friendica\App;
use Friendica\Core\Config\Cache;
use Friendica\Factory;
use Friendica\Util\BasePath;
use Friendica\Util\Config;
class DependencyFactory
{
@ -23,16 +23,17 @@ class DependencyFactory
public static function setUp($channel, $directory, $isBackend = true)
{
$basePath = BasePath::create($directory, $_SERVER);
$configLoader = new Cache\ConfigCacheLoader($basePath);
$mode = new App\Mode($basePath);
$configLoader = new Config\ConfigFileLoader($basePath, $mode);
$configCache = Factory\ConfigFactory::createCache($configLoader);
$profiler = Factory\ProfilerFactory::create($configCache);
Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER);
$config = Factory\ConfigFactory::createConfig($configCache);
// needed to call PConfig::init()
Factory\ConfigFactory::createPConfig($configCache);
$logger = Factory\LoggerFactory::create($channel, $config);
Factory\LoggerFactory::createDev($channel, $config);
$logger = Factory\LoggerFactory::create($channel, $config, $profiler);
Factory\LoggerFactory::createDev($channel, $config, $profiler);
return new App($basePath, $config, $logger, $profiler, $isBackend);
return new App($config, $mode, $logger, $profiler, $isBackend);
}
}

View File

@ -5,9 +5,13 @@ namespace Friendica\Factory;
use Friendica\Core\Config\Configuration;
use Friendica\Core\Logger;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\Logger\FriendicaDevelopHandler;
use Friendica\Util\Logger\FriendicaIntrospectionProcessor;
use Friendica\Util\Logger\WorkerLogger;
use Friendica\Util\Introspection;
use Friendica\Util\Logger\Monolog\DevelopHandler;
use Friendica\Util\Logger\Monolog\IntrospectionProcessor;
use Friendica\Util\Logger\ProfilerLogger;
use Friendica\Util\Logger\StreamLogger;
use Friendica\Util\Logger\SyslogLogger;
use Friendica\Util\Logger\VoidLogger;
use Friendica\Util\Profiler;
use Monolog;
use Psr\Log\LoggerInterface;
@ -27,7 +31,7 @@ class LoggerFactory
private static $ignoreClassList = [
Logger::class,
Profiler::class,
WorkerLogger::class
'Friendica\\Util\\Logger',
];
/**
@ -35,11 +39,27 @@ class LoggerFactory
*
* @param string $channel The channel of the logger instance
* @param Configuration $config The config
* @param Profiler $profiler The profiler of the app
*
* @return LoggerInterface The PSR-3 compliant logger instance
*
* @throws \Exception
* @throws InternalServerErrorException
*/
public static function create($channel, Configuration $config)
public static function create($channel, Configuration $config, Profiler $profiler)
{
if (empty($config->get('system', 'debugging', false))) {
$logger = new VoidLogger();
Logger::init($logger);
return $logger;
}
$introspection = new Introspection(self::$ignoreClassList);
$level = $config->get('system', 'loglevel');
$loglevel = self::mapLegacyConfigDebugLevel((string)$level);
switch ($config->get('system', 'logger_config', 'stream')) {
case 'monolog':
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
@ -47,17 +67,29 @@ class LoggerFactory
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, self::$ignoreClassList));
$logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
$debugging = $config->get('system', 'debugging');
$stream = $config->get('system', 'logfile');
$level = $config->get('system', 'loglevel');
if ($debugging) {
$loglevel = self::mapLegacyConfigDebugLevel((string)$level);
static::addStreamHandler($logger, $stream, $loglevel);
} else {
static::addVoidHandler($logger);
break;
case 'syslog':
$logger = new SyslogLogger($channel, $introspection, $loglevel);
break;
case 'stream':
default:
$stream = $config->get('system', 'logfile');
$logger = new StreamLogger($channel, $stream, $introspection, $loglevel);
break;
}
$profiling = $config->get('system', 'profiling', false);
// In case profiling is enabled, wrap the ProfilerLogger around the current logger
if (isset($profiling) && $profiling !== false) {
$logger = new ProfilerLogger($logger, $profiler);
}
Logger::init($logger);
@ -75,31 +107,63 @@ class LoggerFactory
*
* @param string $channel The channel of the logger instance
* @param Configuration $config The config
* @param Profiler $profiler The profiler of the app
*
* @return LoggerInterface The PSR-3 compliant logger instance
*
* @throws InternalServerErrorException
* @throws \Exception
*/
public static function createDev($channel, Configuration $config)
public static function createDev($channel, Configuration $config, Profiler $profiler)
{
$debugging = $config->get('system', 'debugging');
$stream = $config->get('system', 'dlogfile');
$developerIp = $config->get('system', 'dlogip');
if (!isset($developerIp) || !$debugging) {
return null;
$logger = new VoidLogger();
Logger::setDevLogger($logger);
return $logger;
}
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
$introspection = new Introspection(self::$ignoreClassList);
switch ($config->get('system', 'logger_config', 'stream')) {
case 'monolog':
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
$logger = new Monolog\Logger($channel);
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushProcessor(new FriendicaIntrospectionProcessor(LogLevel::DEBUG, self::$ignoreClassList));
$logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
$logger->pushHandler(new FriendicaDevelopHandler($developerIp));
$logger->pushHandler(new DevelopHandler($developerIp));
static::addStreamHandler($logger, $stream, LogLevel::DEBUG);
break;
case 'syslog':
$logger = new SyslogLogger($channel, $introspection, LogLevel::DEBUG);
break;
case 'stream':
default:
$logger = new StreamLogger($channel, $stream, $introspection, LogLevel::DEBUG);
break;
}
$profiling = $config->get('system', 'profiling', false);
// In case profiling is enabled, wrap the ProfilerLogger around the current logger
if (isset($profiling) && $profiling !== false) {
$logger = new ProfilerLogger($logger, $profiler);
}
Logger::setDevLogger($logger);

9
src/Factory/README.md Normal file
View File

@ -0,0 +1,9 @@
## Friendica\Factory
This namespace contains Factories.
A Factory is used to create specific objects based on its configuration.
See [Factory Method](https://designpatternsphp.readthedocs.io/en/latest/Creational/FactoryMethod/README.html)
Use the classes inside this directory if you want to change the way how new objects should get created.
Don't use the classes to change the behaviour of the concrete objects.

View File

@ -109,6 +109,16 @@ class Contact extends BaseObject
* @}
*/
/**
* @param integer $id
* @return array|boolean Contact record if it exists, false otherwise
* @throws \Exception
*/
public static function getById($id)
{
return DBA::selectFirst('contact', [], ['id' => $id]);
}
/**
* @brief Tests if the given contact is a follower
*

5
src/Model/README.md Normal file
View File

@ -0,0 +1,5 @@
## Friendica\Model
Models are the glue between the business logic of the app and the datastore(s).
In the namespace Model should only be static classes that interact with the DB with the same name as a database table.

38
src/Module/Apps.php Normal file
View File

@ -0,0 +1,38 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Content\Nav;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
/**
* Shows the App menu
*/
class Apps extends BaseModule
{
public static function init()
{
$privateaddons = Config::get('config', 'private_addons');
if ($privateaddons === "1" && !local_user()) {
self::getApp()->internalRedirect();
}
}
public static function content()
{
$apps = Nav::getAppMenu();
if (count($apps) == 0) {
notice(L10n::t('No installed applications.') . EOL);
}
$tpl = Renderer::getMarkupTemplate('apps.tpl');
return Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Applications'),
'$apps' => $apps,
]);
}
}

30
src/Module/Credits.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
/**
* Show a credits page for all the developers who helped with the project
* (only contributors to the git repositories for friendica core and the
* addons repository will be listed though ATM)
*/
class Credits extends BaseModule
{
public static function content()
{
/* fill the page with credits */
$credits_string = file_get_contents('CREDITS.txt');
$names = explode("\n", $credits_string);
$tpl = Renderer::getMarkupTemplate('credits.tpl');
return Renderer::replaceMacros($tpl, [
'$title' => L10n::t('Credits'),
'$thanks' => L10n::t('Friendica is a community project, that would not be possible without the help of many people. Here is a list of those who have contributed to the code or the translation of Friendica. Thank you all!'),
'$names' => $names,
]);
}
}

53
src/Module/Feedtest.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Model;
use Friendica\Protocol;
use Friendica\Util\Network;
/**
* Tests a given feed of a contact
*/
class Feedtest extends BaseModule
{
public static function init()
{
if (!local_user()) {
info(L10n::t('You must be logged in to use this module'));
self::getApp()->internalRedirect();
}
}
public static function content()
{
$result = [];
if (!empty($_REQUEST['url'])) {
$url = $_REQUEST['url'];
$importer = Model\User::getById(local_user());
$contact_id = Model\Contact::getIdForURL($url, local_user(), true);
$contact = Model\Contact::getById($contact_id);
$xml = Network::fetchUrl($contact['poll']);
$dummy = null;
$import_result = Protocol\Feed::import($xml, $importer, $contact, $dummy, true);
$result = [
'input' => $xml,
'output' => var_export($import_result, true),
];
}
$tpl = Renderer::getMarkupTemplate('feedtest.tpl');
return Renderer::replaceMacros($tpl, [
'$url' => ['url', L10n::t('Source URL'), defaults($_REQUEST, 'url', ''), ''],
'$result' => $result
]);
}
}

52
src/Module/Filer.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
use Friendica\Core\Renderer;
use Friendica\Model;
use Friendica\Util\XML;
/**
* Shows a dialog for adding tags to a file
*/
class Filer extends BaseModule
{
public static function init()
{
if (!local_user()) {
info(L10n::t('You must be logged in to use this module'));
self::getApp()->internalRedirect();
}
}
public static function content()
{
$a = self::getApp();
$logger = $a->getLogger();
$term = XML::unescape(trim(defaults($_GET, 'term', '')));
$item_id = (($a->argc > 1) ? intval($a->argv[1]) : 0);
$logger->info('filer', ['tag' => $term, 'item' => $item_id]);
if ($item_id && strlen($term)) {
// file item
Model\FileTag::saveFile(local_user(), $item_id, $term);
info(L10n::t('Filetag %s saved to item', $term));
}
// return filer dialog
$filetags = PConfig::get(local_user(), 'system', 'filetags');
$filetags = Model\FileTag::fileToList($filetags, 'file');
$filetags = explode(",", $filetags);
$tpl = Renderer::getMarkupTemplate("filer_dialog.tpl");
return Renderer::replaceMacros($tpl, [
'$field' => ['term', L10n::t("Save to Folder:"), '', '', $filetags, L10n::t('- select -')],
'$submit' => L10n::t('Save'),
]);
}
}

View File

@ -5,6 +5,7 @@ namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core;
use Friendica\Core\Config\Cache\IConfigCache;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Util\Strings;
@ -65,45 +66,48 @@ class Install extends BaseModule
public static function post()
{
$a = self::getApp();
$configCache = $a->getConfigCache();
switch (self::$currentWizardStep) {
case self::SYSTEM_CHECK:
case self::DATABASE_CONFIG:
// Nothing to do in these steps
self::checkSetting($configCache, $_POST, 'config', 'php_path');
break;
case self::SITE_SETTINGS:
$dbhost = Strings::escapeTags(trim(defaults($_POST, 'dbhost', Core\Installer::DEFAULT_HOST)));
$dbuser = Strings::escapeTags(trim(defaults($_POST, 'dbuser', '')));
$dbpass = Strings::escapeTags(trim(defaults($_POST, 'dbpass', '')));
$dbdata = Strings::escapeTags(trim(defaults($_POST, 'dbdata', '')));
self::checkSetting($configCache, $_POST, 'config', 'php_path');
self::checkSetting($configCache, $_POST, 'database', 'hostname', Core\Installer::DEFAULT_HOST);
self::checkSetting($configCache, $_POST, 'database', 'username', '');
self::checkSetting($configCache, $_POST, 'database', 'password', '');
self::checkSetting($configCache, $_POST, 'database', 'database', '');
// If we cannot connect to the database, return to the previous step
if (!self::$installer->checkDB($a->getBasePath(), $a->getConfigCache(), $a->getProfiler(), $dbhost, $dbuser, $dbpass, $dbdata)) {
if (!self::$installer->checkDB($a->getBasePath(), $configCache, $a->getProfiler())) {
self::$currentWizardStep = self::DATABASE_CONFIG;
}
break;
case self::FINISHED:
$urlpath = $a->getURLPath();
$dbhost = Strings::escapeTags(trim(defaults($_POST, 'dbhost', Core\Installer::DEFAULT_HOST)));
$dbuser = Strings::escapeTags(trim(defaults($_POST, 'dbuser', '')));
$dbpass = Strings::escapeTags(trim(defaults($_POST, 'dbpass', '')));
$dbdata = Strings::escapeTags(trim(defaults($_POST, 'dbdata', '')));
$timezone = Strings::escapeTags(trim(defaults($_POST, 'timezone', Core\Installer::DEFAULT_TZ)));
$language = Strings::escapeTags(trim(defaults($_POST, 'language', Core\Installer::DEFAULT_LANG)));
$adminmail = Strings::escapeTags(trim(defaults($_POST, 'adminmail', '')));
self::checkSetting($configCache, $_POST, 'config', 'php_path');
self::checkSetting($configCache, $_POST, 'database', 'hostname', Core\Installer::DEFAULT_HOST);
self::checkSetting($configCache, $_POST, 'database', 'username', '');
self::checkSetting($configCache, $_POST, 'database', 'password', '');
self::checkSetting($configCache, $_POST, 'database', 'database', '');
self::checkSetting($configCache, $_POST, 'system', 'default_timezone', Core\Installer::DEFAULT_TZ);
self::checkSetting($configCache, $_POST, 'system', 'language', Core\Installer::DEFAULT_LANG);
self::checkSetting($configCache, $_POST, 'config', 'admin_email', '');
// If we cannot connect to the database, return to the Database config wizard
if (!self::$installer->checkDB($a->getBasePath(), $a->getConfigCache(), $a->getProfiler(), $dbhost, $dbuser, $dbpass, $dbdata)) {
if (!self::$installer->checkDB($a->getBasePath(), $configCache, $a->getProfiler())) {
self::$currentWizardStep = self::DATABASE_CONFIG;
return;
}
$phpath = self::$installer->getPHPPath();
if (!self::$installer->createConfig($phpath, $urlpath, $dbhost, $dbuser, $dbpass, $dbdata, $timezone, $language, $adminmail, $a->getBasePath())) {
if (!self::$installer->createConfig($a, $configCache, $a->getBasePath())) {
return;
}
@ -116,6 +120,7 @@ class Install extends BaseModule
public static function content()
{
$a = self::getApp();
$configCache = $a->getConfigCache();
$output = '';
@ -123,9 +128,9 @@ class Install extends BaseModule
switch (self::$currentWizardStep) {
case self::SYSTEM_CHECK:
$phppath = defaults($_POST, 'phpath', null);
$php_path = $configCache->get('config', 'php_path');
$status = self::$installer->checkEnvironment($a->getBaseURL(), $phppath);
$status = self::$installer->checkEnvironment($a->getBaseURL(), $php_path);
$tpl = Renderer::getMarkupTemplate('install_checks.tpl');
$output .= Renderer::replaceMacros($tpl, [
@ -136,19 +141,12 @@ class Install extends BaseModule
'$see_install' => L10n::t('Please see the file "INSTALL.txt".'),
'$next' => L10n::t('Next'),
'$reload' => L10n::t('Check again'),
'$phpath' => $phppath,
'$php_path' => $php_path,
'$baseurl' => $a->getBaseURL()
]);
break;
case self::DATABASE_CONFIG:
$dbhost = Strings::escapeTags(trim(defaults($_POST, 'dbhost' , Core\Installer::DEFAULT_HOST)));
$dbuser = Strings::escapeTags(trim(defaults($_POST, 'dbuser' , '' )));
$dbpass = Strings::escapeTags(trim(defaults($_POST, 'dbpass' , '' )));
$dbdata = Strings::escapeTags(trim(defaults($_POST, 'dbdata' , '' )));
$phpath = Strings::escapeTags(trim(defaults($_POST, 'phpath' , '' )));
$adminmail = Strings::escapeTags(trim(defaults($_POST, 'adminmail', '' )));
$tpl = Renderer::getMarkupTemplate('install_db.tpl');
$output .= Renderer::replaceMacros($tpl, [
'$title' => $install_title,
@ -157,51 +155,35 @@ class Install extends BaseModule
'$info_02' => L10n::t('Please contact your hosting provider or site administrator if you have questions about these settings.'),
'$info_03' => L10n::t('The database you specify below should already exist. If it does not, please create it before continuing.'),
'checks' => self::$installer->getChecks(),
'$dbhost' => ['dbhost',
'$dbhost' => ['database-hostname',
L10n::t('Database Server Name'),
$dbhost,
$configCache->get('database', 'hostname'),
'',
'required'],
'$dbuser' => ['dbuser',
'$dbuser' => ['database-username',
L10n::t('Database Login Name'),
$dbuser,
$configCache->get('database', 'username'),
'',
'required',
'autofocus'],
'$dbpass' => ['dbpass',
'$dbpass' => ['database-password',
L10n::t('Database Login Password'),
$dbpass,
$configCache->get('database', 'password'),
L10n::t("For security reasons the password must not be empty"),
'required'],
'$dbdata' => ['dbdata',
'$dbdata' => ['database-database',
L10n::t('Database Name'),
$dbdata,
$configCache->get('database', 'database'),
'',
'required'],
'$adminmail' => ['adminmail',
L10n::t('Site administrator email address'),
$adminmail,
L10n::t('Your account email address must match this in order to use the web admin panel.'),
'required',
'autofocus',
'email'],
'$lbl_10' => L10n::t('Please select a default timezone for your website'),
'$baseurl' => $a->getBaseURL(),
'$phpath' => $phpath,
'$php_path' => $configCache->get('config', 'php_path'),
'$submit' => L10n::t('Submit')
]);
break;
case self::SITE_SETTINGS:
$dbhost = Strings::escapeTags(trim(defaults($_POST, 'dbhost', Core\Installer::DEFAULT_HOST)));
$dbuser = Strings::escapeTags(trim(defaults($_POST, 'dbuser', '' )));
$dbpass = Strings::escapeTags(trim(defaults($_POST, 'dbpass', '' )));
$dbdata = Strings::escapeTags(trim(defaults($_POST, 'dbdata', '' )));
$phpath = Strings::escapeTags(trim(defaults($_POST, 'phpath', '' )));
$adminmail = Strings::escapeTags(trim(defaults($_POST, 'adminmail', '')));
$timezone = defaults($_POST, 'timezone', Core\Installer::DEFAULT_TZ);
/* Installed langs */
$lang_choices = L10n::getAvailableLanguages();
@ -210,16 +192,23 @@ class Install extends BaseModule
'$title' => $install_title,
'$checks' => self::$installer->getChecks(),
'$pass' => L10n::t('Site settings'),
'$dbhost' => $dbhost,
'$dbuser' => $dbuser,
'$dbpass' => $dbpass,
'$dbdata' => $dbdata,
'$phpath' => $phpath,
'$adminmail' => ['adminmail', L10n::t('Site administrator email address'), $adminmail, L10n::t('Your account email address must match this in order to use the web admin panel.'), 'required', 'autofocus', 'email'],
'$timezone' => Temporal::getTimezoneField('timezone', L10n::t('Please select a default timezone for your website'), $timezone, ''),
'$language' => ['language',
'$dbhost' => $configCache->get('database', 'hostname'),
'$dbuser' => $configCache->get('database', 'username'),
'$dbpass' => $configCache->get('database', 'password'),
'$dbdata' => $configCache->get('database', 'database'),
'$phpath' => $configCache->get('config', 'php_path'),
'$adminmail' => ['config-admin_email',
L10n::t('Site administrator email address'),
$configCache->get('config', 'admin_email'),
L10n::t('Your account email address must match this in order to use the web admin panel.'),
'required', 'autofocus', 'email'],
'$timezone' => Temporal::getTimezoneField('system-default_timezone',
L10n::t('Please select a default timezone for your website'),
$configCache->get('system', 'default_timezone'),
''),
'$language' => ['system-language',
L10n::t('System Language:'),
Core\Installer::DEFAULT_LANG,
$configCache->get('system', 'language'),
L10n::t('Set the default language for your Friendica installation interface and to send emails.'),
$lang_choices],
'$baseurl' => $a->getBaseURL(),
@ -269,4 +258,24 @@ class Install extends BaseModule
. L10n::t('Go to your new Friendica node <a href="%s/register">registration page</a> and register as new user. Remember to use the same email you have entered as administrator email. This will allow you to enter the site admin panel.', $baseurl)
. "</p>";
}
/**
* Checks the $_POST settings and updates the config Cache for it
*
* @param IConfigCache $configCache The current config cache
* @param array $post The $_POST data
* @param string $cat The category of the setting
* @param string $key The key of the setting
* @param null|string $default The default value
*/
private static function checkSetting(IConfigCache $configCache, array $post, $cat, $key, $default = null)
{
$configCache->set($cat, $key,
Strings::escapeTags(
trim(defaults($post, sprintf('%s-%s', $cat, $key),
(!isset($default) ? $configCache->get($cat, $key) : $default))
)
)
);
}
}

10
src/Module/README.md Normal file
View File

@ -0,0 +1,10 @@
## Friendica\Module
The Module namespace contains the different modules of Friendica.
Each module is loaded through the [`App`](https://github.com/friendica/friendica/blob/develop/src/App.php).
Rules for Modules:
- Named like the call (i.e. https://friendica.test/contact => `Contact`)
- Start with capitals and are **not** camelCased.
- Directly interacting with a given request (POST or GET)
- Extending [`BaseModule`](https://github.com/friendica/friendica/blob/develop/src/BaseModule.php).

5
src/Object/README.md Normal file
View File

@ -0,0 +1,5 @@
## Friendica\Object
The namespace Object contains dynamic classes which are **not** directly interacting with the datastore.
They are used to implement business logic for a particular object (i.e. an Image).

View File

@ -1,41 +1,44 @@
<?php
namespace Friendica\Core\Config\Cache;
namespace Friendica\Util\Config;
use Friendica\App;
use Friendica\Core\Addon;
use Friendica\Core\Config\Cache\IConfigCache;
/**
* The ConfigCacheLoader loads config-files and stores them in a ConfigCache ( @see ConfigCache )
* The ConfigFileLoader loads config-files and stores them in a IConfigCache ( @see IConfigCache )
*
* It is capable of loading the following config files:
* - *.config.php (current)
* - *.ini.php (deprecated)
* - *.htconfig.php (deprecated)
*/
class ConfigCacheLoader
class ConfigFileLoader extends ConfigFileManager
{
/**
* The Sub directory of the config-files
* @var string
* @var App\Mode
*/
const SUBDIRECTORY = 'config';
private $appMode;
private $baseDir;
private $configDir;
public function __construct($baseDir)
public function __construct($baseDir, App\Mode $mode)
{
$this->baseDir = $baseDir;
$this->configDir = $baseDir . DIRECTORY_SEPARATOR . self::SUBDIRECTORY;
parent::__construct($baseDir);
$this->appMode = $mode;
}
/**
* Load the configuration files
* Load the configuration files into an configuration cache
*
* First loads the default value for all the configuration keys, then the legacy configuration files, then the
* expected local.config.php
*
* @param IConfigCache $config The config cache to load to
* @param bool $raw Setup the raw config format
*
* @throws \Exception
*/
public function loadConfigFiles(ConfigCache $config)
public function setupCache(IConfigCache $config, $raw = false)
{
$config->load($this->loadCoreConfig('defaults'));
$config->load($this->loadCoreConfig('settings'));
@ -44,23 +47,29 @@ class ConfigCacheLoader
$config->load($this->loadLegacyConfig('htconfig'), true);
$config->load($this->loadCoreConfig('local'), true);
// In case of install mode, add the found basepath (because there isn't a basepath set yet
if (!$raw && ($this->appMode->isInstall() || empty($config->get('system', 'basepath')))) {
// Setting at least the basepath we know
$config->set('system', 'basepath', $this->baseDir);
}
}
/**
* Tries to load the specified core-configuration and returns the config array.
*
* @param string $name The name of the configuration
* @param string $name The name of the configuration (default is empty, which means 'local')
*
* @return array The config array (empty if no config found)
*
* @throws \Exception if the configuration file isn't readable
*/
public function loadCoreConfig($name)
public function loadCoreConfig($name = '')
{
if (file_exists($this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php')) {
return $this->loadConfigFile($this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php');
} elseif (file_exists($this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php')) {
return $this->loadINIConfigFile($this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php');
if (!empty($this->getConfigFullName($name))) {
return $this->loadConfigFile($this->getConfigFullName($name));
} elseif (!empty($this->getIniFullName($name))) {
return $this->loadINIConfigFile($this->getIniFullName($name));
} else {
return [];
}
@ -93,22 +102,19 @@ class ConfigCacheLoader
/**
* Tries to load the legacy config files (.htconfig.php, .htpreconfig.php) and returns the config array.
*
* @param string $name The name of the config file
* @param string $name The name of the config file (default is empty, which means .htconfig.php)
*
* @return array The configuration array (empty if no config found)
*
* @deprecated since version 2018.09
*/
private function loadLegacyConfig($name)
private function loadLegacyConfig($name = '')
{
$filePath = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php';
$config = [];
if (file_exists($filePath)) {
if (!empty($this->getHtConfigFullName($name))) {
$a = new \stdClass();
$a->config = [];
include $filePath;
include $this->getHtConfigFullName($name);
$htConfigCategories = array_keys($a->config);

View File

@ -0,0 +1,90 @@
<?php
namespace Friendica\Util\Config;
/**
* An abstract class in case of handling with config files
*/
abstract class ConfigFileManager
{
/**
* The Sub directory of the config-files
* @var string
*/
const SUBDIRECTORY = 'config';
/**
* The default name of the user defined config file
* @var string
*/
const CONFIG_LOCAL = 'local';
/**
* The default name of the user defined ini file
* @var string
*/
const CONFIG_INI = 'local';
/**
* The default name of the user defined legacy config file
* @var string
*/
const CONFIG_HTCONFIG = 'htconfig';
protected $baseDir;
protected $configDir;
/**
* @param string $baseDir The base directory of Friendica
*/
public function __construct($baseDir)
{
$this->baseDir = $baseDir;
$this->configDir = $baseDir . DIRECTORY_SEPARATOR . self::SUBDIRECTORY;
}
/**
* Gets the full name (including the path) for a *.config.php (default is local.config.php)
*
* @param string $name The config name (default is empty, which means local.config.php)
*
* @return string The full name or empty if not found
*/
protected function getConfigFullName($name = '')
{
$name = !empty($name) ? $name : self::CONFIG_LOCAL;
$fullName = $this->configDir . DIRECTORY_SEPARATOR . $name . '.config.php';
return file_exists($fullName) ? $fullName : '';
}
/**
* Gets the full name (including the path) for a *.ini.php (default is local.ini.php)
*
* @param string $name The config name (default is empty, which means local.ini.php)
*
* @return string The full name or empty if not found
*/
protected function getIniFullName($name = '')
{
$name = !empty($name) ? $name : self::CONFIG_INI;
$fullName = $this->configDir . DIRECTORY_SEPARATOR . $name . '.ini.php';
return file_exists($fullName) ? $fullName : '';
}
/**
* Gets the full name (including the path) for a .*.php (default is .htconfig.php)
*
* @param string $name The config name (default is empty, which means .htconfig.php)
*
* @return string The full name or empty if not found
*/
protected function getHtConfigFullName($name = '')
{
$name = !empty($name) ? $name : self::CONFIG_HTCONFIG;
$fullName = $this->baseDir . DIRECTORY_SEPARATOR . '.' . $name . '.php';
return file_exists($fullName) ? $fullName : '';
}
}

View File

@ -0,0 +1,341 @@
<?php
namespace Friendica\Util\Config;
/**
* The ConfigFileSaver saves specific variables into the config-files
*
* It is capable of loading the following config files:
* - *.config.php (current)
* - *.ini.php (deprecated)
* - *.htconfig.php (deprecated)
*/
class ConfigFileSaver extends ConfigFileManager
{
/**
* The standard indentation for config files
* @var string
*/
const INDENT = "\t";
/**
* The settings array to save to
* @var array
*/
private $settings = [];
/**
* Adds a given value to the config file
* Either it replaces the current value or it will get added
*
* @param string $cat The configuration category
* @param string $key The configuration key
* @param string $value The new value
*/
public function addConfigValue($cat, $key, $value)
{
$settingsCount = count(array_keys($this->settings));
for ($i = 0; $i < $settingsCount; $i++) {
// if already set, overwrite the value
if ($this->settings[$i]['cat'] === $cat &&
$this->settings[$i]['key'] === $key) {
$this->settings[$i] = ['cat' => $cat, 'key' => $key, 'value' => $value];
return;
}
}
$this->settings[] = ['cat' => $cat, 'key' => $key, 'value' => $value];
}
/**
* Resetting all added configuration entries so far
*/
public function reset()
{
$this->settings = [];
}
/**
* Save all added configuration entries to the given config files
* After updating the config entries, all configuration entries will be reseted
*
* @param string $name The name of the configuration file (default is empty, which means the default name each type)
*
* @return bool true, if at least one configuration file was successfully updated or nothing to do
*/
public function saveToConfigFile($name = '')
{
// If no settings et, return true
if (count(array_keys($this->settings)) === 0) {
return true;
}
$saved = false;
// Check for the *.config.php file inside the /config/ path
list($reading, $writing) = $this->openFile($this->getConfigFullName($name));
if (isset($reading) && isset($writing)) {
$this->saveConfigFile($reading, $writing);
// Close the current file handler and rename them
if ($this->closeFile($this->getConfigFullName($name), $reading, $writing)) {
// just return true, if everything went fine
$saved = true;
}
}
// Check for the *.ini.php file inside the /config/ path
list($reading, $writing) = $this->openFile($this->getIniFullName($name));
if (isset($reading) && isset($writing)) {
$this->saveINIConfigFile($reading, $writing);
// Close the current file handler and rename them
if ($this->closeFile($this->getIniFullName($name), $reading, $writing)) {
// just return true, if everything went fine
$saved = true;
}
}
// Check for the *.php file (normally .htconfig.php) inside the / path
list($reading, $writing) = $this->openFile($this->getHtConfigFullName($name));
if (isset($reading) && isset($writing)) {
$this->saveToLegacyConfig($reading, $writing);
// Close the current file handler and rename them
if ($this->closeFile($this->getHtConfigFullName($name), $reading, $writing)) {
// just return true, if everything went fine
$saved = true;
}
}
$this->reset();
return $saved;
}
/**
* Opens a config file and returns two handler for reading and writing
*
* @param string $fullName The full name of the current config
*
* @return array An array containing the two reading and writing handler
*/
private function openFile($fullName)
{
if (empty($fullName)) {
return [null, null];
}
try {
$reading = fopen($fullName, 'r');
} catch (\Exception $exception) {
return [null, null];
}
if (!$reading) {
return [null, null];
}
try {
$writing = fopen($fullName . '.tmp', 'w');
} catch (\Exception $exception) {
fclose($reading);
return [null, null];
}
if (!$writing) {
fclose($reading);
return [null, null];
}
return [$reading, $writing];
}
/**
* Close and rename the config file
*
* @param string $fullName The full name of the current config
* @param resource $reading The reading resource handler
* @param resource $writing The writing resource handler
*
* @return bool True, if the close was successful
*/
private function closeFile($fullName, $reading, $writing)
{
fclose($reading);
fclose($writing);
try {
$renamed = rename($fullName, $fullName . '.old');
} catch (\Exception $exception) {
return false;
}
if (!$renamed) {
return false;
}
try {
$renamed = rename($fullName . '.tmp', $fullName);
} catch (\Exception $exception) {
// revert the move of the current config file to have at least the old config
rename($fullName . '.old', $fullName);
return false;
}
if (!$renamed) {
// revert the move of the current config file to have at least the old config
rename($fullName . '.old', $fullName);
return false;
}
return true;
}
/**
* Saves all configuration values to a config file
*
* @param resource $reading The reading handler
* @param resource $writing The writing handler
*/
private function saveConfigFile($reading, $writing)
{
$settingsCount = count(array_keys($this->settings));
$categoryFound = array_fill(0, $settingsCount, false);
$categoryBracketFound = array_fill(0, $settingsCount, false);;
$lineFound = array_fill(0, $settingsCount, false);;
$lineArrowFound = array_fill(0, $settingsCount, false);;
while (!feof($reading)) {
$line = fgets($reading);
// check for each added setting if we have to replace a config line
for ($i = 0; $i < $settingsCount; $i++) {
// find the first line like "'system' =>"
if (!$categoryFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['cat']))) {
$categoryFound[$i] = true;
}
// find the first line with a starting bracket ( "[" )
if ($categoryFound[$i] && !$categoryBracketFound[$i] && stristr($line, '[')) {
$categoryBracketFound[$i] = true;
}
// find the first line with the key like "'value'"
if ($categoryBracketFound[$i] && !$lineFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['key']))) {
$lineFound[$i] = true;
}
// find the first line with an arrow ("=>") after finding the key
if ($lineFound[$i] && !$lineArrowFound[$i] && stristr($line, '=>')) {
$lineArrowFound[$i] = true;
}
// find the current value and replace it
if ($lineArrowFound[$i] && preg_match_all('/\'(.*?)\'/', $line, $matches, PREG_SET_ORDER)) {
$lineVal = end($matches)[0];
$line = str_replace($lineVal, '\'' . $this->settings[$i]['value'] . '\'', $line);
$categoryFound[$i] = false;
$categoryBracketFound[$i] = false;
$lineFound[$i] = false;
$lineArrowFound[$i] = false;
// if a line contains a closing bracket for the category ( "]" ) and we didn't find the key/value pair,
// add it as a new line before the closing bracket
} elseif ($categoryBracketFound[$i] && !$lineArrowFound[$i] && stristr($line, ']')) {
$categoryFound[$i] = false;
$categoryBracketFound[$i] = false;
$lineFound[$i] = false;
$lineArrowFound[$i] = false;
$newLine = sprintf(self::INDENT . self::INDENT . '\'%s\' => \'%s\',' . PHP_EOL, $this->settings[$i]['key'], $this->settings[$i]['value']);
$line = $newLine . $line;
}
}
fputs($writing, $line);
}
}
/**
* Saves a value to a ini file
*
* @param resource $reading The reading handler
* @param resource $writing The writing handler
*/
private function saveINIConfigFile($reading, $writing)
{
$settingsCount = count(array_keys($this->settings));
$categoryFound = array_fill(0, $settingsCount, false);
while (!feof($reading)) {
$line = fgets($reading);
// check for each added setting if we have to replace a config line
for ($i = 0; $i < $settingsCount; $i++) {
// find the category of the current setting
if (!$categoryFound[$i] && stristr($line, sprintf('[%s]', $this->settings[$i]['cat']))) {
$categoryFound[$i] = true;
// check the current value
} elseif ($categoryFound[$i] && preg_match_all('/^' . $this->settings[$i]['key'] . '\s*=\s*(.*?)$/', $line, $matches, PREG_SET_ORDER)) {
$line = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL;
$categoryFound[$i] = false;
// If end of INI file, add the line before the INI end
} elseif ($categoryFound[$i] && (preg_match_all('/^\[.*?\]$/', $line) || preg_match_all('/^INI;.*$/', $line))) {
$categoryFound[$i] = false;
$newLine = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL;
$line = $newLine . $line;
}
}
fputs($writing, $line);
}
}
/**
* Saves a value to a .php file (normally .htconfig.php)
*
* @param resource $reading The reading handler
* @param resource $writing The writing handler
*/
private function saveToLegacyConfig($reading, $writing)
{
$settingsCount = count(array_keys($this->settings));
$found = array_fill(0, $settingsCount, false);
while (!feof($reading)) {
$line = fgets($reading);
// check for each added setting if we have to replace a config line
for ($i = 0; $i < $settingsCount; $i++) {
// check for a non plain config setting (use category too)
if ($this->settings[$i]['cat'] !== 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['cat'] . '\'\]\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) {
$line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
$found[$i] = true;
// check for a plain config setting (don't use a category)
} elseif ($this->settings[$i]['cat'] === 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) {
$line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
$found[$i] = true;
}
}
fputs($writing, $line);
}
for ($i = 0; $i < $settingsCount; $i++) {
if (!$found[$i]) {
if ($this->settings[$i]['cat'] !== 'config') {
$line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
} else {
$line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL;
}
fputs($writing, $line);
}
}
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Friendica\Util;
/**
* Get Introspection information about the current call
*/
class Introspection
{
private $skipStackFramesCount;
private $skipClassesPartials;
private $skipFunctions = [
'call_user_func',
'call_user_func_array',
];
/**
* @param array $skipClassesPartials An array of classes to skip during logging
* @param int $skipStackFramesCount If the logger should use information from other hierarchy levels of the call
*/
public function __construct($skipClassesPartials = array(), $skipStackFramesCount = 0)
{
$this->skipClassesPartials = $skipClassesPartials;
$this->skipStackFramesCount = $skipStackFramesCount;
}
/**
* Adds new classes to get skipped
* @param array $classNames
*/
public function addClasses(array $classNames)
{
$this->skipClassesPartials = array_merge($this->skipClassesPartials, $classNames);
}
/**
* Returns the introspection record of the current call
*
* @return array
*/
public function getRecord()
{
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$i = 1;
while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
$i++;
}
$i += $this->skipStackFramesCount;
return [
'file' => isset($trace[$i - 1]['file']) ? basename($trace[$i - 1]['file']) : null,
'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
];
}
/**
* Checks if the current trace class or function has to be skipped
*
* @param array $trace The current trace array
* @param int $index The index of the current hierarchy level
*
* @return bool True if the class or function should get skipped, otherwise false
*/
private function isTraceClassOrSkippedFunction(array $trace, $index)
{
if (!isset($trace[$index])) {
return false;
}
if (isset($trace[$index]['class'])) {
foreach ($this->skipClassesPartials as $part) {
if (strpos($trace[$index]['class'], $part) !== false) {
return true;
}
}
} elseif (in_array($trace[$index]['function'], $this->skipFunctions)) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,158 @@
<?php
namespace Friendica\Util\Logger;
use Friendica\Util\Introspection;
use Friendica\Util\Strings;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* This class contains all necessary dependencies and calls for Friendica
* Every new Logger should extend this class and define, how addEntry() works
*
* Additional information for each Logger, who extends this class:
* - Introspection
* - UID for each call
* - Channel of the current call (i.e. index, worker, daemon, ...)
*/
abstract class AbstractLogger implements LoggerInterface
{
/**
* The output channel of this logger
* @var string
*/
protected $channel;
/**
* The Introspection for the current call
* @var Introspection
*/
protected $introspection;
/**
* The UID of the current call
* @var string
*/
protected $logUid;
/**
* Adds a new entry to the log
*
* @param int $level
* @param string $message
* @param array $context
*
* @return void
*/
abstract protected function addEntry($level, $message, $context = []);
/**
* @param string $channel The output channel
* @param Introspection $introspection The introspection of the current call
*
* @throws \Exception
*/
public function __construct($channel, Introspection $introspection)
{
$this->channel = $channel;
$this->introspection = $introspection;
$this->logUid = Strings::getRandomHex(6);
}
/**
* Simple interpolation of PSR-3 compliant replacements ( variables between '{' and '}' )
* @see https://www.php-fig.org/psr/psr-3/#12-message
*
* @param string $message
* @param array $context
*
* @return string the interpolated message
*/
protected function psrInterpolate($message, array $context = array())
{
$replace = [];
foreach ($context as $key => $value) {
// check that the value can be casted to string
if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
$replace['{' . $key . '}'] = $value;
} elseif (is_array($value)) {
$replace['{' . $key . '}'] = @json_encode($value);
}
}
return strtr($message, $replace);
}
/**
* {@inheritdoc}
*/
public function emergency($message, array $context = array())
{
$this->addEntry(LogLevel::EMERGENCY, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function alert($message, array $context = array())
{
$this->addEntry(LogLevel::ALERT, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function critical($message, array $context = array())
{
$this->addEntry(LogLevel::CRITICAL, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function error($message, array $context = array())
{
$this->addEntry(LogLevel::ERROR, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function warning($message, array $context = array())
{
$this->addEntry(LogLevel::WARNING, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function notice($message, array $context = array())
{
$this->addEntry(LogLevel::NOTICE, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function info($message, array $context = array())
{
$this->addEntry(LogLevel::INFO, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function debug($message, array $context = array())
{
$this->addEntry(LogLevel::DEBUG, (string) $message, $context);
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
$this->addEntry($level, (string) $message, $context);
}
}

View File

@ -1,94 +0,0 @@
<?php
namespace Friendica\Util\Logger;
use Monolog\Logger;
use Monolog\Processor\ProcessorInterface;
/**
* Injects line/file//function where the log message came from
*
* Based on the class IntrospectionProcessor without the "class" information
* @see IntrospectionProcessor
*/
class FriendicaIntrospectionProcessor implements ProcessorInterface
{
private $level;
private $skipStackFramesCount;
private $skipClassesPartials;
private $skipFunctions = [
'call_user_func',
'call_user_func_array',
];
/**
* @param string|int $level The minimum logging level at which this Processor will be triggered
* @param array $skipClassesPartials An array of classes to skip during logging
* @param int $skipStackFramesCount If the logger should use information from other hierarchy levels of the call
*/
public function __construct($level = Logger::DEBUG, $skipClassesPartials = array(), $skipStackFramesCount = 0)
{
$this->level = Logger::toMonologLevel($level);
$this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials);
$this->skipStackFramesCount = $skipStackFramesCount;
}
public function __invoke(array $record)
{
// return if the level is not high enough
if ($record['level'] < $this->level) {
return $record;
}
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
$i = 1;
while ($this->isTraceClassOrSkippedFunction($trace, $i)) {
$i++;
}
$i += $this->skipStackFramesCount;
// we should have the call source now
$record['extra'] = array_merge(
$record['extra'],
[
'file' => isset($trace[$i - 1]['file']) ? basename($trace[$i - 1]['file']) : null,
'line' => isset($trace[$i - 1]['line']) ? $trace[$i - 1]['line'] : null,
'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
]
);
return $record;
}
/**
* Checks if the current trace class or function has to be skipped
*
* @param array $trace The current trace array
* @param int $index The index of the current hierarchy level
* @return bool True if the class or function should get skipped, otherwise false
*/
private function isTraceClassOrSkippedFunction(array $trace, $index)
{
if (!isset($trace[$index])) {
return false;
}
if (isset($trace[$index]['class'])) {
foreach ($this->skipClassesPartials as $part) {
if (strpos($trace[$index]['class'], $part) !== false) {
return true;
}
}
} elseif (in_array($trace[$index]['function'], $this->skipFunctions)) {
return true;
}
return false;
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace Friendica\Util\Logger;
namespace Friendica\Util\Logger\Monolog;
use Monolog\Handler;
use Monolog\Logger;
@ -11,7 +11,7 @@ use Monolog\Logger;
* If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
* you'll use Logger::develop() for the duration of your work, and you clean it up when you're done before submitting your PR.
*/
class FriendicaDevelopHandler extends Handler\AbstractHandler
class DevelopHandler extends Handler\AbstractHandler
{
/**
* @var string The IP of the developer who wants to debug

View File

@ -0,0 +1,43 @@
<?php
namespace Friendica\Util\Logger\Monolog;
use Friendica\Util\Introspection;
use Monolog\Logger;
use Monolog\Processor\ProcessorInterface;
/**
* Injects line/file//function where the log message came from
*/
class IntrospectionProcessor implements ProcessorInterface
{
private $level;
private $introspection;
/**
* @param Introspection $introspection Holds the Introspection of the current call
* @param string|int $level The minimum logging level at which this Processor will be triggered
*/
public function __construct(Introspection $introspection, $level = Logger::DEBUG)
{
$this->level = Logger::toMonologLevel($level);
$introspection->addClasses(array('Monolog\\'));
$this->introspection = $introspection;
}
public function __invoke(array $record)
{
// return if the level is not high enough
if ($record['level'] < $this->level) {
return $record;
}
// we should have the call source now
$record['extra'] = array_merge(
$record['extra'],
$this->introspection->getRecord()
);
return $record;
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace Friendica\Util\Logger;
use Friendica\Core\System;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* This Logger adds additional profiling data in case profiling is enabled.
* It uses a predefined logger.
*/
class ProfilerLogger implements LoggerInterface
{
/**
* The Logger of the current call
* @var LoggerInterface
*/
private $logger;
/**
* The Profiler for the current call
* @var Profiler
*/
protected $profiler;
/**
* ProfilerLogger constructor.
* @param LoggerInterface $logger The Logger of the current call
* @param Profiler $profiler The profiler of the current call
*/
public function __construct(LoggerInterface $logger, Profiler $profiler)
{
$this->logger = $logger;
$this->profiler = $profiler;
}
/**
* {@inheritdoc}
*/
public function emergency($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->emergency($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function alert($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->alert($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function critical($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->critical($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function error($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->error($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function warning($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->warning($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function notice($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->notice($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function info($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->info($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function debug($message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->debug($message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
$stamp1 = microtime(true);
$this->logger->log($level, $message, $context);
$this->profiler->saveTimestamp($stamp1, 'file', System::callstack());
}
}

27
src/Util/Logger/README.md Normal file
View File

@ -0,0 +1,27 @@
## Friendica\Util\Logger
This namespace contains the different implementations of a Logger.
### Configuration guideline
The following settings are possible for `logger_config`:
- `monolog`: A Logging framework with lots of additions (see [Monolog](https://github.com/Seldaek/monolog/)). There are just Friendica additions inside the Monolog directory
- [`stream`](StreamLogger.php): A small logger for files or streams
- [`syslog`](SyslogLogger.php): Prints the logging output into the syslog
[`VoidLogger`](VoidLogger.php) is a fallback logger without any function if no debugging is enabled.
[`ProfilerLogger`](ProfilerLogger.php) is a wrapper around an existing logger in case profiling is enabled for Friendica.
Every log call will be saved to the `Profiler` with a timestamp.
### Implementation guideline
Each logging implementation should pe capable of printing at least the following information:
- An unique ID for each Request/Call
- The process ID (PID)
- A timestamp of the logging entry
- The critically of the log entry
- A log message
- A context of the log message (f.e which user)
If possible, a Logger should extend [`AbstractLogger`](AbstractLogger.php), because it contains additional, Friendica specific business logic for each logging call.

View File

@ -0,0 +1,197 @@
<?php
namespace Friendica\Util\Logger;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Introspection;
use Psr\Log\LogLevel;
/**
* A Logger instance for logging into a stream (file, stdout, stderr)
*/
class StreamLogger extends AbstractLogger
{
/**
* The minimum loglevel at which this logger will be triggered
* @var string
*/
private $logLevel;
/**
* The file URL of the stream (if needed)
* @var string
*/
private $url;
/**
* The stream, where the current logger is writing into
* @var resource
*/
private $stream;
/**
* The current process ID
* @var int
*/
private $pid;
/**
* An error message
* @var string
*/
private $errorMessage;
/**
* Translates LogLevel log levels to integer values
* @var array
*/
private $levelToInt = [
LogLevel::EMERGENCY => 0,
LogLevel::ALERT => 1,
LogLevel::CRITICAL => 2,
LogLevel::ERROR => 3,
LogLevel::WARNING => 4,
LogLevel::NOTICE => 5,
LogLevel::INFO => 6,
LogLevel::DEBUG => 7,
];
/**
* {@inheritdoc}
* @param string|resource $stream The stream to write with this logger (either a file or a stream, i.e. stdout)
* @param string $level The minimum loglevel at which this logger will be triggered
*
* @throws \Exception
*/
public function __construct($channel, $stream, Introspection $introspection, $level = LogLevel::DEBUG)
{
parent::__construct($channel, $introspection);
if (is_resource($stream)) {
$this->stream = $stream;
} elseif (is_string($stream)) {
$this->url = $stream;
} else {
throw new \InvalidArgumentException('A stream must either be a resource or a string.');
}
$this->pid = getmypid();
if (array_key_exists($level, $this->levelToInt)) {
$this->logLevel = $this->levelToInt[$level];
} else {
throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
}
}
public function close()
{
if ($this->url && is_resource($this->stream)) {
fclose($this->stream);
}
$this->stream = null;
}
/**
* Adds a new entry to the log
*
* @param int $level
* @param string $message
* @param array $context
*
* @return void
*/
protected function addEntry($level, $message, $context = [])
{
if (!array_key_exists($level, $this->levelToInt)) {
throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
}
$logLevel = $this->levelToInt[$level];
if ($logLevel > $this->logLevel) {
return;
}
$this->checkStream();
$formattedLog = $this->formatLog($level, $message, $context);
fwrite($this->stream, $formattedLog);
}
/**
* Formats a log record for the syslog output
*
* @param int $level The loglevel/priority
* @param string $message The message
* @param array $context The context of this call
*
* @return string the formatted syslog output
*/
private function formatLog($level, $message, $context = [])
{
$record = $this->introspection->getRecord();
$record = array_merge($record, ['uid' => $this->logUid, 'process_id' => $this->pid]);
$logMessage = '';
$logMessage .= DateTimeFormat::utcNow() . ' ';
$logMessage .= $this->channel . ' ';
$logMessage .= '[' . strtoupper($level) . ']: ';
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
$logMessage .= @json_encode($context) . ' - ';
$logMessage .= @json_encode($record);
$logMessage .= PHP_EOL;
return $logMessage;
}
private function checkStream()
{
if (is_resource($this->stream)) {
return;
}
if (empty($this->url)) {
throw new \LogicException('Missing stream URL.');
}
$this->createDir();
set_error_handler([$this, 'customErrorHandler']);
$this->stream = fopen($this->url, 'ab');
restore_error_handler();
if (!is_resource($this->stream)) {
$this->stream = null;
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: ' . $this->errorMessage, $this->url));
}
}
private function createDir()
{
$dirname = null;
$pos = strpos($this->url, '://');
if (!$pos) {
$dirname = dirname($this->url);
}
if (substr($this->url, 0, 7) === 'file://') {
$dirname = dirname(substr($this->url, 7));
}
if (isset($dirname) && !is_dir($dirname)) {
set_error_handler([$this, 'customErrorHandler']);
$status = mkdir($dirname, 0777, true);
restore_error_handler();
if (!$status && !is_dir($dirname)) {
throw new \UnexpectedValueException(sprintf('Directory "%s" cannot get created: ' . $this->errorMessage, $dirname));
}
}
}
private function customErrorHandler($code, $msg)
{
$this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
}
}

View File

@ -0,0 +1,206 @@
<?php
namespace Friendica\Util\Logger;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\Introspection;
use Psr\Log\LogLevel;
/**
* A Logger instance for syslogging (fast, but simple)
* @see http://php.net/manual/en/function.syslog.php
*/
class SyslogLogger extends AbstractLogger
{
const IDENT = 'Friendica';
/**
* Translates LogLevel log levels to syslog log priorities.
* @var array
*/
private $logLevels = [
LogLevel::DEBUG => LOG_DEBUG,
LogLevel::INFO => LOG_INFO,
LogLevel::NOTICE => LOG_NOTICE,
LogLevel::WARNING => LOG_WARNING,
LogLevel::ERROR => LOG_ERR,
LogLevel::CRITICAL => LOG_CRIT,
LogLevel::ALERT => LOG_ALERT,
LogLevel::EMERGENCY => LOG_EMERG,
];
/**
* Translates log priorities to string outputs
* @var array
*/
private $logToString = [
LOG_DEBUG => 'DEBUG',
LOG_INFO => 'INFO',
LOG_NOTICE => 'NOTICE',
LOG_WARNING => 'WARNING',
LOG_ERR => 'ERROR',
LOG_CRIT => 'CRITICAL',
LOG_ALERT => 'ALERT',
LOG_EMERG => 'EMERGENCY'
];
/**
* Indicates what logging options will be used when generating a log message
* @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
*
* @var int
*/
private $logOpts;
/**
* Used to specify what type of program is logging the message
* @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
*
* @var int
*/
private $logFacility;
/**
* The minimum loglevel at which this logger will be triggered
* @var int
*/
private $logLevel;
/**
* A error message of the current operation
* @var string
*/
private $errorMessage;
/**
* {@inheritdoc}
* @param string $level The minimum loglevel at which this logger will be triggered
* @param int $logOpts Indicates what logging options will be used when generating a log message
* @param int $logFacility Used to specify what type of program is logging the message
*
* @throws \Exception
*/
public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
{
parent::__construct($channel, $introspection);
$this->logOpts = $logOpts;
$this->logFacility = $logFacility;
$this->logLevel = $this->mapLevelToPriority($level);
$this->introspection->addClasses(array(self::class));
}
/**
* Adds a new entry to the syslog
*
* @param int $level
* @param string $message
* @param array $context
*
* @throws InternalServerErrorException if the syslog isn't available
*/
protected function addEntry($level, $message, $context = [])
{
$logLevel = $this->mapLevelToPriority($level);
if ($logLevel > $this->logLevel) {
return;
}
$formattedLog = $this->formatLog($logLevel, $message, $context);
$this->write($logLevel, $formattedLog);
}
/**
* Maps the LogLevel (@see LogLevel ) to a SysLog priority (@see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters )
*
* @param string $level A LogLevel
*
* @return int The SysLog priority
*
* @throws \Psr\Log\InvalidArgumentException If the loglevel isn't valid
*/
public function mapLevelToPriority($level)
{
if (!array_key_exists($level, $this->logLevels)) {
throw new \InvalidArgumentException(sprintf('The level "%s" is not valid.', $level));
}
return $this->logLevels[$level];
}
/**
* Closes the Syslog
*/
public function close()
{
closelog();
}
/**
* Writes a message to the syslog
* @see http://php.net/manual/en/function.syslog.php#refsect1-function.syslog-parameters
*
* @param int $priority The Priority
* @param string $message The message of the log
*
* @throws InternalServerErrorException if syslog cannot be used
*/
private function write($priority, $message)
{
set_error_handler([$this, 'customErrorHandler']);
$opened = openlog(self::IDENT, $this->logOpts, $this->logFacility);
restore_error_handler();
if (!$opened) {
throw new \UnexpectedValueException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
}
$this->syslogWrapper($priority, $message);
}
/**
* Formats a log record for the syslog output
*
* @param int $level The loglevel/priority
* @param string $message The message
* @param array $context The context of this call
*
* @return string the formatted syslog output
*/
private function formatLog($level, $message, $context = [])
{
$record = $this->introspection->getRecord();
$record = array_merge($record, ['uid' => $this->logUid]);
$logMessage = '';
$logMessage .= $this->channel . ' ';
$logMessage .= '[' . $this->logToString[$level] . ']: ';
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
$logMessage .= @json_encode($context) . ' - ';
$logMessage .= @json_encode($record);
return $logMessage;
}
private function customErrorHandler($code, $msg)
{
$this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
}
/**
* A syslog wrapper to make syslog functionality testable
*
* @param int $level The syslog priority
* @param string $entry The message to send to the syslog function
*/
protected function syslogWrapper($level, $entry)
{
set_error_handler([$this, 'customErrorHandler']);
$written = syslog($level, $entry);
restore_error_handler();
if (!$written) {
throw new \UnexpectedValueException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
}
}
}

View File

@ -0,0 +1,140 @@
<?php
namespace Friendica\Util\Logger;
use Psr\Log\LoggerInterface;
/**
* A Logger instance to not log
*/
class VoidLogger implements LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
return;
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array())
{
return;
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array())
{
return;
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array())
{
return;
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array())
{
return;
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array())
{
return;
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array())
{
return;
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array())
{
return;
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*/
public function log($level, $message, array $context = array())
{
return;
}
}

View File

@ -6,12 +6,16 @@
namespace Friendica\Worker;
use Friendica\BaseObject;
use Friendica\Core\Config;
use Friendica\Core\Update;
class DBUpdate extends BaseObject
{
public static function execute()
{
// Just in case the last update wasn't failed
if (Config::get('system', 'update', Update::SUCCESS, true) != Update::FAILED) {
Update::run(self::getApp()->getBasePath());
}
}
}

View File

@ -550,7 +550,7 @@ class Notifier
*/
private static function isRemovalActivity($cmd, $owner, $network)
{
return ($cmd == Delivery::DELETION) && $owner['account_removed'] && in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DIASPORA]);
return ($cmd == Delivery::DELETION) && $owner['account_removed'] && in_array($network, [Protocol::ACTIVITYPUB, Protocol::DIASPORA]);
}
/**

6
src/Worker/README.md Normal file
View File

@ -0,0 +1,6 @@
## Friendica\Worker
The Worker namespace contains all asynchronous workers of Friendica.
The all have to implement the function `public static function execute()`.
They are all executed by the [`Worker`](https://github.com/friendica/friendica/blob/develop/src/Core/Worker.php).

View File

@ -5,10 +5,11 @@
namespace Friendica\Test;
use Friendica\Core\Config\Cache;
use Friendica\App;
use Friendica\Database\DBA;
use Friendica\Factory;
use Friendica\Util\BasePath;
use Friendica\Util\Config\ConfigFileLoader;
use Friendica\Util\Profiler;
use PHPUnit\DbUnit\DataSet\YamlDataSet;
use PHPUnit\DbUnit\TestCaseTrait;
@ -41,7 +42,8 @@ abstract class DatabaseTest extends MockedTest
}
$basePath = BasePath::create(dirname(__DIR__));
$configLoader = new Cache\ConfigCacheLoader($basePath);
$mode = new App\Mode($basePath);
$configLoader = new ConfigFileLoader($basePath, $mode);
$config = Factory\ConfigFactory::createCache($configLoader);
$profiler = \Mockery::mock(Profiler::class);

View File

@ -30,19 +30,27 @@ trait AppMockTrait
*/
protected $profilerMock;
/**
* @var MockInterface|App\Mode The mocked App mode
*/
protected $mode;
/**
* Mock the App
*
* @param vfsStreamDirectory $root The root directory
* @param Config\Cache\ConfigCache $configCache
* @param bool $raw If true, no config mocking will be done
*/
public function mockApp($root)
public function mockApp(vfsStreamDirectory $root, $configCache = null, $raw = false)
{
$this->configMock = \Mockery::mock(Config\Cache\IConfigCache::class);
$this->mode = \Mockery::mock(App\Mode::class);
$configAdapterMock = \Mockery::mock(Config\Adapter\IConfigAdapter::class);
// Disable the adapter
$configAdapterMock->shouldReceive('isConnected')->andReturn(false);
$config = new Config\Configuration($this->configMock, $configAdapterMock);
$config = new Config\Configuration((isset($configCache) ? $configCache : $this->configMock), $configAdapterMock);
// Initialize empty Config
Config::init($config);
@ -52,6 +60,37 @@ trait AppMockTrait
->shouldReceive('getBasePath')
->andReturn($root->url());
$this->app
->shouldReceive('getMode')
->andReturn($this->mode);
$this->profilerMock = \Mockery::mock(Profiler::class);
$this->profilerMock->shouldReceive('saveTimestamp');
$this->app
->shouldReceive('getConfigCache')
->andReturn((isset($configCache) ? $configCache : $this->configMock));
$this->app
->shouldReceive('getTemplateEngine')
->andReturn(new FriendicaSmartyEngine());
$this->app
->shouldReceive('getCurrentTheme')
->andReturn('Smarty3');
$this->app
->shouldReceive('getProfiler')
->andReturn($this->profilerMock);
$this->app
->shouldReceive('getBaseUrl')
->andReturnUsing(function () {
return $this->app->getConfigCache()->get('system', 'url');
});
BaseObject::setApp($this->app);
if ($raw) {
return;
}
$this->configMock
->shouldReceive('has')
->andReturn(true);
@ -79,26 +118,5 @@ trait AppMockTrait
->shouldReceive('get')
->with('system', 'theme')
->andReturn('system_theme');
$this->profilerMock = \Mockery::mock(Profiler::class);
$this->profilerMock->shouldReceive('saveTimestamp');
$this->app
->shouldReceive('getConfigCache')
->andReturn($this->configMock);
$this->app
->shouldReceive('getTemplateEngine')
->andReturn(new FriendicaSmartyEngine());
$this->app
->shouldReceive('getCurrentTheme')
->andReturn('Smarty3');
$this->app
->shouldReceive('getBaseUrl')
->andReturn('http://friendica.local');
$this->app
->shouldReceive('getProfiler')
->andReturn($this->profilerMock);
BaseObject::setApp($this->app);
}
}

View File

@ -36,16 +36,20 @@ trait RendererMockTrait
* Mocking the method 'Renderer::replaceMacros()'
*
* @param string $template The template to use (normally, it is the mock result of 'mockGetMarkupTemplate()'
* @param array $args The arguments to pass to the macro
* @param array|\Closure|null $args The arguments to pass to the macro
* @param string $return the return value of the mock
* @param null|int $times How often the method will get used
*/
public function mockReplaceMacros($template, $args = [], $return = '', $times = null)
public function mockReplaceMacros($template, $args = null, $return = '', $times = null)
{
if (!isset($this->rendererMock)) {
$this->rendererMock = \Mockery::mock('alias:' . Renderer::class);
}
if (!isset($args)) {
$args = [];
}
$this->rendererMock
->shouldReceive('replaceMacros')
->with($template, $args)

View File

@ -25,7 +25,7 @@ trait VFSTrait
];
// create a virtual directory and copy all needed files and folders to it
$this->root = vfsStream::setup('friendica', null, $structure);
$this->root = vfsStream::setup('friendica', 0777, $structure);
$this->setConfigFile('defaults.config.php');
$this->setConfigFile('settings.config.php');

View File

@ -33,7 +33,7 @@ $a->config['sitename'] = "Friendica My Network";
// must precisely match the email address of the person logged in.
$a->config['register_policy'] = REGISTER_OPEN;
$a->config['register_text'] = 'A register text';
$a->config['admin_email'] = 'admin@friendica.local';
$a->config['admin_email'] = 'admin@test.it';
$a->config['admin_nickname'] = 'Friendly admin';
// Maximum size of an imported message, 0 is unlimited
@ -52,7 +52,7 @@ $a->config['system']['huburl'] = '[internal]';
$a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero';
// default system theme
$a->config['system']['theme'] = 'duepuntozero';
$a->config['system']['theme'] = 'frio';
// By default allow pseudonyms
$a->config['system']['no_regfullname'] = true;

View File

@ -23,5 +23,6 @@ return [
'system' => [
'default_timezone' => 'UTC',
'language' => 'en',
'theme' => 'frio',
],
];

View File

@ -11,6 +11,9 @@ username = testuser
password = testpw
database = testdb
[system]
theme = frio
[config]
admin_email = admin@test.it
INI;

View File

@ -7,13 +7,13 @@ namespace Friendica\Test;
use Friendica\App;
use Friendica\Core\Config;
use Friendica\Core\Config\Cache;
use Friendica\Core\PConfig;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Factory;
use Friendica\Network\HTTPException;
use Friendica\Util\BasePath;
use Friendica\Util\Config\ConfigFileLoader;
use Monolog\Handler\TestHandler;
require_once __DIR__ . '/../../include/api.php';
@ -31,20 +31,33 @@ class ApiTest extends DatabaseTest
*/
protected $logOutput;
/** @var App */
protected $app;
/** @var array */
protected $selfUser;
/** @var array */
protected $friendUser;
/** @var array */
protected $otherUser;
protected $wrongUserId;
/**
* Create variables used by tests.
*/
public function setUp()
{
$basePath = BasePath::create(dirname(__DIR__) . '/../');
$configLoader = new Cache\ConfigCacheLoader($basePath);
$mode = new App\Mode($basePath);
$configLoader = new ConfigFileLoader($basePath, $mode);
$configCache = Factory\ConfigFactory::createCache($configLoader);
$profiler = Factory\ProfilerFactory::create($configCache);
Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER);
$config = Factory\ConfigFactory::createConfig($configCache);
Factory\ConfigFactory::createPConfig($configCache);
$logger = Factory\LoggerFactory::create('test', $config);
$this->app = new App($basePath, $config, $logger, $profiler, false);
$logger = Factory\LoggerFactory::create('test', $config, $profiler);
$this->app = new App($config, $mode, $logger, $profiler, false);
parent::setUp();
@ -1271,31 +1284,30 @@ class ApiTest extends DatabaseTest
/**
* Test the api_status_show() function.
* @return void
*/
public function testApiStatusShow()
public function testApiStatusShowWithJson()
{
$result = api_status_show('json');
$result = api_status_show('json', 1);
$this->assertStatus($result['status']);
}
/**
* Test the api_status_show() function with an XML result.
* @return void
*/
public function testApiStatusShowWithXml()
{
$result = api_status_show('xml');
$result = api_status_show('xml', 1);
$this->assertXml($result, 'statuses');
}
/**
* Test the api_status_show() function with a raw result.
* @return void
* Test the api_get_last_status() function
*/
public function testApiStatusShowWithRaw()
public function testApiGetLastStatus()
{
$this->assertStatus(api_status_show('raw'));
$item = api_get_last_status($this->selfUser['id'], $this->selfUser['id']);
$this->assertNotNull($item);
}
/**

View File

@ -0,0 +1,69 @@
<?php
/**
* Created by PhpStorm.
* User: benlo
* Date: 25/03/19
* Time: 21:36
*/
namespace Friendica\Test\src\Content;
use Friendica\Content\Smilies;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\AppMockTrait;
use Friendica\Test\Util\L10nMockTrait;
use Friendica\Test\Util\VFSTrait;
class SmiliesTest extends MockedTest
{
use VFSTrait;
use AppMockTrait;
use L10nMockTrait;
protected function setUp()
{
parent::setUp();
$this->setUpVfsDir();
$this->mockApp($this->root);
$this->app->videowidth = 425;
$this->app->videoheight = 350;
$this->configMock->shouldReceive('get')
->with('system', 'no_smilies')
->andReturn(false);
$this->configMock->shouldReceive('get')
->with(false, 'system', 'no_smilies')
->andReturn(false);
}
public function dataLinks()
{
return [
/** @see https://github.com/friendica/friendica/pull/6933 */
'bug-6933-1' => [
'data' => '<code>/</code>',
'smilies' => ['texts' => [], 'icons' => []],
'expected' => '<code>/</code>',
],
'bug-6933-2' => [
'data' => '<code>code</code>',
'smilies' => ['texts' => [], 'icons' => []],
'expected' => '<code>code</code>',
],
];
}
/**
* Test replace smilies in different texts
* @dataProvider dataLinks
*
* @param string $text Test string
* @param array $smilies List of smilies to replace
* @param string $expected Expected result
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public function testReplaceFromArray($text, $smilies, $expected)
{
$output = Smilies::replaceFromArray($text, $smilies);
$this->assertEquals($expected, $output);
}
}

View File

@ -37,6 +37,9 @@ class BBCodeTest extends MockedTest
$this->configMock->shouldReceive('get')
->with('system', 'itemcache_duration')
->andReturn(-1);
$this->configMock->shouldReceive('get')
->with('system', 'url')
->andReturn('friendica.local');
$this->mockL10nT();
}

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Cache;
use Friendica\Core\Cache;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
use Friendica\Test\Util\DbaCacheMockTrait;
/**

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Cache;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
/**
* @requires extension memcache

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Cache;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
/**
* @requires extension memcached

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Cache;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
/**
* @requires extension redis

View File

@ -2,11 +2,15 @@
namespace Friendica\Test\src\Core\Console;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Core\Console\AutomaticInstallation;
use Friendica\Core\Installer;
use Friendica\Core\Logger;
use Friendica\Test\Util\DBAMockTrait;
use Friendica\Test\Util\DBStructureMockTrait;
use Friendica\Test\Util\L10nMockTrait;
use Friendica\Test\Util\RendererMockTrait;
use Friendica\Util\Logger\VoidLogger;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamFile;
@ -22,12 +26,6 @@ class AutomaticInstallationConsoleTest extends ConsoleTest
use DBStructureMockTrait;
use RendererMockTrait;
private $db_host;
private $db_port;
private $db_data;
private $db_user;
private $db_pass;
/**
* @var vfsStreamFile Assert file without DB credentials
*/
@ -37,6 +35,11 @@ class AutomaticInstallationConsoleTest extends ConsoleTest
*/
private $assertFileDb;
/**
* @var ConfigCache The configuration cache to check after each test
*/
private $configCache;
public function setUp()
{
parent::setUp();
@ -46,42 +49,104 @@ class AutomaticInstallationConsoleTest extends ConsoleTest
->removeChild('local.config.php');
}
$this->db_host = getenv('MYSQL_HOST');
$this->db_port = !empty(getenv('MYSQL_PORT')) ? getenv('MYSQL_PORT') : null;
$this->db_data = getenv('MYSQL_DATABASE');
$this->db_user = getenv('MYSQL_USERNAME') . getenv('MYSQL_USER');
$this->db_pass = getenv('MYSQL_PASSWORD');
$this->configMock
->shouldReceive('get')
->with('config', 'php_path')
->andReturn(false);
$this->mockL10nT();
$this->configCache = new ConfigCache();
$this->configCache->set('system', 'basepath', $this->root->url());
$this->configCache->set('config', 'php_path', trim(shell_exec('which php')));
$this->configCache->set('system', 'theme', 'smarty3');
$this->mockApp($this->root, null, true);
$this->configMock->shouldReceive('set')->andReturnUsing(function ($cat, $key, $value) {
if ($key !== 'basepath') {
return $this->configCache->set($cat, $key, $value);
} else {
return true;
}
});
$this->configMock->shouldReceive('has')->andReturn(true);
$this->configMock->shouldReceive('get')->andReturnUsing(function ($cat, $key) {
return $this->configCache->get($cat, $key);
});
$this->configMock->shouldReceive('load')->andReturnUsing(function ($config, $overwrite = false) {
return $this->configCache->load($config, $overwrite);
});
$this->mode->shouldReceive('isInstall')->andReturn(true);
Logger::init(new VoidLogger());
}
/**
* Creates the arguments which is asserted to be passed to 'replaceMacros()' for creating the local.config.php
* Returns the dataset for each automatic installation test
*
* @param bool $withDb if true, DB will get saved too
*
* @return array The arguments to pass to the mock for 'replaceMacros()'
* @return array the dataset
*/
private function createArgumentsForMacro($withDb)
public function dataInstaller()
{
$args = [
'$phpath' => trim(shell_exec('which php')),
'$dbhost' => (($withDb) ? $this->db_host . (isset($this->db_port) ? ':' . $this->db_port : '') : ''),
'$dbuser' => (($withDb) ? $this->db_user : ''),
'$dbpass' => (($withDb) ? $this->db_pass : ''),
'$dbdata' => (($withDb) ? $this->db_data : ''),
'$timezone' => 'Europe/Berlin',
'$language' => 'de',
'$urlpath' => '/friendica',
'$adminmail' => 'admin@friendica.local'
return [
'empty' => [
'data' => [
'database' => [
'hostname' => '',
'username' => '',
'password' => '',
'database' => '',
'port' => '',
],
'config' => [
'php_path' => '',
'admin_email' => '',
],
'system' => [
'urlpath' => '',
'default_timezone' => '',
'language' => '',
],
],
],
'normal' => [
'data' => [
'database' => [
'hostname' => 'testhost',
'port' => 3306,
'username' => 'friendica',
'password' => 'a password',
'database' => 'database',
],
'config' => [
'php_path' => '',
'admin_email' => 'admin@philipp.info',
],
'system' => [
'urlpath' => 'test/it',
'default_timezone' => 'en',
'language' => 'Europe/Berlin',
],
],
],
'special' => [
'data' => [
'database' => [
'hostname' => 'testhost.new.domain',
'port' => 3341,
'username' => 'fr"§%ica',
'password' => '$%\"gse',
'database' => 'db',
],
'config' => [
'php_path' => '',
'admin_email' => 'admin@philipp.info',
],
'system' => [
'urlpath' => 'test/it',
'default_timezone' => 'en',
'language' => 'Europe/Berlin',
],
],
],
];
return $args;
}
private function assertFinished($txt, $withconfig = false, $copyfile = false)
@ -177,15 +242,95 @@ FIN;
}
/**
* @medium
* Asserts one config entry
*
* @param string $cat The category to test
* @param string $key The key to test
* @param null|array $assertion The asserted value (null = empty, or array/string)
* @param string $default_value The default value
*/
public function testWithConfig()
public function assertConfigEntry($cat, $key, $assertion = null, $default_value = null)
{
if (!empty($assertion[$cat][$key])) {
$this->assertEquals($assertion[$cat][$key], $this->configCache->get($cat, $key));
} elseif (!empty($assertion) && !is_array($assertion)) {
$this->assertEquals($assertion, $this->configCache->get($cat, $key));
} elseif (!empty($default_value)) {
$this->assertEquals($default_value, $this->configCache->get($cat, $key));
} else {
$this->assertEmpty($this->configCache->get($cat, $key), $this->configCache->get($cat, $key));
}
}
/**
* Asserts all config entries
*
* @param null|array $assertion The optional assertion array
* @param boolean $saveDb True, if the db credentials should get saved to the file
* @param boolean $default True, if we use the default values
* @param boolean $defaultDb True, if we use the default value for the DB
*/
public function assertConfig($assertion = null, $saveDb = false, $default = true, $defaultDb = true)
{
if (!empty($assertion['database']['hostname'])) {
$assertion['database']['hostname'] .= (!empty($assertion['database']['port']) ? ':' . $assertion['database']['port'] : '');
}
$this->assertConfigEntry('database', 'hostname', ($saveDb) ? $assertion : null, (!$saveDb || $defaultDb) ? Installer::DEFAULT_HOST : null);
$this->assertConfigEntry('database', 'username', ($saveDb) ? $assertion : null);
$this->assertConfigEntry('database', 'password', ($saveDb) ? $assertion : null);
$this->assertConfigEntry('database', 'database', ($saveDb) ? $assertion : null);
$this->assertConfigEntry('config', 'admin_email', $assertion);
$this->assertConfigEntry('config', 'php_path', trim(shell_exec('which php')));
$this->assertConfigEntry('system', 'default_timezone', $assertion, ($default) ? Installer::DEFAULT_TZ : null);
$this->assertConfigEntry('system', 'language', $assertion, ($default) ? Installer::DEFAULT_LANG : null);
}
/**
* Test the automatic installation without any parameter/setting
*/
public function testEmpty()
{
$this->app->shouldReceive('getURLPath')->andReturn('')->atLeast()->once();
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
$this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', \Mockery::any(), '', 1);
$console = new AutomaticInstallation($this->consoleArgv);
$txt = $this->dumpExecute($console);
$this->assertFinished($txt, true, false);
$this->assertTrue($this->root->hasChild('config' . DIRECTORY_SEPARATOR . 'local.config.php'));
$this->assertConfig();
}
/**
* Test the automatic installation with a prepared config file
* @dataProvider dataInstaller
*/
public function testWithConfig(array $data)
{
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
$this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$conf = function ($cat, $key) use ($data) {
if ($cat == 'database' && $key == 'hostname' && !empty($data['database']['port'])) {
return $data[$cat][$key] . ':' . $data['database']['port'];
}
return $data[$cat][$key];
};
$config = <<<CONF
<?php
@ -196,10 +341,10 @@ FIN;
return [
'database' => [
'hostname' => '',
'username' => '',
'password' => '',
'database' => '',
'hostname' => '{$conf('database', 'hostname')}',
'username' => '{$conf('database', 'username')}',
'password' => '{$conf('database', 'password')}',
'database' => '{$conf('database', 'database')}',
'charset' => 'utf8mb4',
],
@ -210,14 +355,15 @@ return [
// ****************************************************************
'config' => [
'admin_email' => '',
'admin_email' => '{$conf('config', 'admin_email')}',
'sitename' => 'Friendica Social Network',
'register_policy' => \Friendica\Module\Register::OPEN,
'register_text' => '',
],
'system' => [
'default_timezone' => 'UTC',
'language' => 'en',
'urlpath' => '{$conf('system', 'urlpath')}',
'default_timezone' => '{$conf('system', 'default_timezone')}',
'language' => '{$conf('system', 'language')}',
],
];
CONF;
@ -234,25 +380,39 @@ CONF;
$this->assertFinished($txt, false, true);
$this->assertTrue($this->root->hasChild('config' . DIRECTORY_SEPARATOR . 'local.config.php'));
$this->assertEquals($config, file_get_contents($this->root->getChild('config' . DIRECTORY_SEPARATOR . 'local.config.php')->url()));
$this->assertConfig($data, true, false, false);
}
/**
* @medium
* Test the automatic installation with environment variables
* Includes saving the DB credentials to the file
* @dataProvider dataInstaller
*/
public function testWithEnvironmentAndSave()
public function testWithEnvironmentAndSave(array $data)
{
$this->app->shouldReceive('getURLPath')->andReturn('')->atLeast()->once();
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
$this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(true), '', 1);
$this->mockReplaceMacros('testTemplate', \Mockery::any(), '', 1);
$this->assertTrue(putenv('FRIENDICA_ADMIN_MAIL=admin@friendica.local'));
$this->assertTrue(putenv('FRIENDICA_TZ=Europe/Berlin'));
$this->assertTrue(putenv('FRIENDICA_LANG=de'));
$this->assertTrue(putenv('FRIENDICA_URL_PATH=/friendica'));
$this->assertTrue(putenv('MYSQL_HOST=' . $data['database']['hostname']));
$this->assertTrue(putenv('MYSQL_PORT=' . $data['database']['port']));
$this->assertTrue(putenv('MYSQL_DATABASE=' . $data['database']['database']));
$this->assertTrue(putenv('MYSQL_USERNAME=' . $data['database']['username']));
$this->assertTrue(putenv('MYSQL_PASSWORD=' . $data['database']['password']));
$this->assertTrue(putenv('FRIENDICA_URL_PATH=' . $data['system']['urlpath']));
$this->assertTrue(putenv('FRIENDICA_PHP_PATH=' . $data['config']['php_path']));
$this->assertTrue(putenv('FRIENDICA_ADMIN_MAIL=' . $data['config']['admin_email']));
$this->assertTrue(putenv('FRIENDICA_TZ=' . $data['system']['default_timezone']));
$this->assertTrue(putenv('FRIENDICA_LANG=' . $data['system']['language']));
$console = new AutomaticInstallation($this->consoleArgv);
$console->setOption('savedb', true);
@ -260,90 +420,105 @@ CONF;
$txt = $this->dumpExecute($console);
$this->assertFinished($txt, true);
$this->assertConfig($data, true, true, false);
}
/**
* @medium
* Test the automatic installation with environment variables
* Don't save the db credentials to the file
* @dataProvider dataInstaller
*/
public function testWithEnvironmentWithoutSave()
public function testWithEnvironmentWithoutSave(array $data)
{
$this->app->shouldReceive('getURLPath')->andReturn('')->atLeast()->once();
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
$this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(false), '', 1);
$this->mockReplaceMacros('testTemplate', \Mockery::any(), '', 1);
$this->assertTrue(putenv('FRIENDICA_ADMIN_MAIL=admin@friendica.local'));
$this->assertTrue(putenv('FRIENDICA_TZ=Europe/Berlin'));
$this->assertTrue(putenv('FRIENDICA_LANG=de'));
$this->assertTrue(putenv('FRIENDICA_URL_PATH=/friendica'));
$this->assertTrue(putenv('MYSQL_HOST=' . $data['database']['hostname']));
$this->assertTrue(putenv('MYSQL_PORT=' . $data['database']['port']));
$this->assertTrue(putenv('MYSQL_DATABASE=' . $data['database']['database']));
$this->assertTrue(putenv('MYSQL_USERNAME=' . $data['database']['username']));
$this->assertTrue(putenv('MYSQL_PASSWORD=' . $data['database']['password']));
$this->assertTrue(putenv('FRIENDICA_URL_PATH=' . $data['system']['urlpath']));
$this->assertTrue(putenv('FRIENDICA_PHP_PATH=' . $data['config']['php_path']));
$this->assertTrue(putenv('FRIENDICA_ADMIN_MAIL=' . $data['config']['admin_email']));
$this->assertTrue(putenv('FRIENDICA_TZ=' . $data['system']['default_timezone']));
$this->assertTrue(putenv('FRIENDICA_LANG=' . $data['system']['language']));
$console = new AutomaticInstallation($this->consoleArgv);
$txt = $this->dumpExecute($console);
$this->assertFinished($txt, true);
$this->assertConfig($data, false, true);
}
/**
* @medium
* Test the automatic installation with arguments
* @dataProvider dataInstaller
*/
public function testWithArguments()
public function testWithArguments(array $data)
{
$this->app->shouldReceive('getURLPath')->andReturn('')->atLeast()->once();
$this->mockConnect(true, 1);
$this->mockConnected(true, 1);
$this->mockExistsTable('user', false, 1);
$this->mockUpdate([$this->root->url(), false, true, true], null, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(true), '', 1);
$this->mockReplaceMacros('testTemplate', \Mockery::any(), '', 1);
$console = new AutomaticInstallation($this->consoleArgv);
$console->setOption('dbhost', $this->db_host);
$console->setOption('dbuser', $this->db_user);
if (!empty($this->db_pass)) {
$console->setOption('dbpass', $this->db_pass);
$option = function($var, $cat, $key) use ($data, $console) {
if (!empty($data[$cat][$key])) {
$console->setOption($var, $data[$cat][$key]);
}
if (!empty($this->db_port)) {
$console->setOption('dbport', $this->db_port);
}
$console->setOption('dbdata', $this->db_data);
$console->setOption('admin', 'admin@friendica.local');
$console->setOption('tz', 'Europe/Berlin');
$console->setOption('lang', 'de');
$console->setOption('urlpath', '/friendica');
};
$option('dbhost' , 'database', 'hostname');
$option('dbport' , 'database', 'port');
$option('dbuser' , 'database', 'username');
$option('dbpass' , 'database', 'password');
$option('dbdata' , 'database', 'database');
$option('urlpath' , 'system' , 'urlpath');
$option('phppath' , 'config' , 'php_path');
$option('admin' , 'config' , 'admin_email');
$option('tz' , 'system' , 'default_timezone');
$option('lang' , 'system' , 'language');
$txt = $this->dumpExecute($console);
$this->assertFinished($txt, true);
$this->assertConfig($data, true, true, true);
}
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* Test the automatic installation with a wrong database connection
*/
public function testNoDatabaseConnection()
{
$this->app->shouldReceive('getURLPath')->andReturn('')->atLeast()->once();
$this->mockConnect(false, 1);
$this->mockGetMarkupTemplate('local.config.tpl', 'testTemplate', 1);
$this->mockReplaceMacros('testTemplate', $this->createArgumentsForMacro(false), '', 1);
$this->assertTrue(putenv('FRIENDICA_ADMIN_MAIL=admin@friendica.local'));
$this->assertTrue(putenv('FRIENDICA_TZ=Europe/Berlin'));
$this->assertTrue(putenv('FRIENDICA_LANG=de'));
$this->assertTrue(putenv('FRIENDICA_URL_PATH=/friendica'));
$this->mockReplaceMacros('testTemplate', \Mockery::any(), '', 1);
$console = new AutomaticInstallation($this->consoleArgv);
$txt = $this->dumpExecute($console);
$this->assertStuckDB($txt);
$this->assertTrue($this->root->hasChild('config' . DIRECTORY_SEPARATOR . 'local.config.php'));
$this->assertConfig(null, false, true, false);
}
public function testGetHelp()
@ -406,6 +581,6 @@ HELP;
$txt = $this->dumpExecute($console);
$this->assertEquals($txt, $theHelp);
$this->assertEquals($theHelp, $txt);
}
}

View File

@ -16,20 +16,18 @@ class ConfigConsoleTest extends ConsoleTest
{
parent::setUp();
$this->mockApp($this->root);
\Mockery::getConfiguration()->setConstantsMap([
Mode::class => [
'DBCONFIGAVAILABLE' => 0
]
]);
$mode = \Mockery::mock(Mode::class);
$mode
$this->mode
->shouldReceive('has')
->andReturn(true);
$this->app
->shouldReceive('getMode')
->andReturn($mode);
}
function testSetGetKeyValue() {

View File

@ -22,14 +22,9 @@ abstract class ConsoleTest extends MockedTest
{
parent::setUp();
if (!getenv('MYSQL_DATABASE')) {
$this->markTestSkipped('Please set the MYSQL_* environment variables to your test database credentials.');
}
Intercept::setUp();
$this->setUpVfsDir();
$this->mockApp($this->root);
}
/**

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Lock;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
use Friendica\Core\Lock\CacheLockDriver;
/**

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Lock;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
use Friendica\Core\Lock\CacheLockDriver;
/**

View File

@ -3,7 +3,7 @@
namespace Friendica\Test\src\Core\Lock;
use Friendica\Core\Cache\CacheDriverFactory;
use Friendica\Factory\CacheDriverFactory;
use Friendica\Core\Lock\CacheLockDriver;
/**

View File

@ -3,25 +3,26 @@ namespace Friendica\Test\src\Database;
use Friendica\App;
use Friendica\Core\Config;
use Friendica\Core\Config\Cache;
use Friendica\Database\DBA;
use Friendica\Factory;
use Friendica\Test\DatabaseTest;
use Friendica\Util\BasePath;
use Friendica\Util\Config\ConfigFileLoader;
class DBATest extends DatabaseTest
{
public function setUp()
{
$basePath = BasePath::create(dirname(__DIR__) . '/../../');
$configLoader = new Cache\ConfigCacheLoader($basePath);
$mode = new App\Mode($basePath);
$configLoader = new ConfigFileLoader($basePath, $mode);
$configCache = Factory\ConfigFactory::createCache($configLoader);
$profiler = Factory\ProfilerFactory::create($configCache);
Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER);
$config = Factory\ConfigFactory::createConfig($configCache);
Factory\ConfigFactory::createPConfig($configCache);
$logger = Factory\LoggerFactory::create('test', $config);
$this->app = new App($basePath, $config, $logger, $profiler, false);
$logger = Factory\LoggerFactory::create('test', $config, $profiler);
$this->app = new App($config, $mode, $logger, $profiler, false);
parent::setUp();

View File

@ -3,25 +3,26 @@
namespace Friendica\Test\src\Database;
use Friendica\App;
use Friendica\Core\Config\Cache;
use Friendica\Database\DBStructure;
use Friendica\Factory;
use Friendica\Test\DatabaseTest;
use Friendica\Util\BasePath;
use Friendica\Util\Config\ConfigFileLoader;
class DBStructureTest extends DatabaseTest
{
public function setUp()
{
$basePath = BasePath::create(dirname(__DIR__) . '/../../');
$configLoader = new Cache\ConfigCacheLoader($basePath);
$mode = new App\Mode($basePath);
$configLoader = new ConfigFileLoader($basePath, $mode);
$configCache = Factory\ConfigFactory::createCache($configLoader);
$profiler = Factory\ProfilerFactory::create($configCache);
Factory\DBFactory::init($basePath, $configCache, $profiler, $_SERVER);
$config = Factory\ConfigFactory::createConfig($configCache);
Factory\ConfigFactory::createPConfig($configCache);
$logger = Factory\LoggerFactory::create('test', $config);
$this->app = new App($basePath, $config, $logger, $profiler, false);
$logger = Factory\LoggerFactory::create('test', $config, $profiler);
$this->app = new App($config, $mode, $logger, $profiler, false);
parent::setUp();
}

View File

@ -2,11 +2,20 @@
namespace Friendica\Test\src\Network;
use Friendica\Core\Logger;
use Friendica\Network\CurlResult;
use Friendica\Util\Logger\VoidLogger;
use PHPUnit\Framework\TestCase;
class CurlResultTest extends TestCase
{
protected function setUp()
{
parent::setUp();
Logger::init(new VoidLogger());
}
/**
* @small
*/

View File

@ -1,22 +1,45 @@
<?php
namespace Friendica\Test\src\Core\Config\Cache;
namespace Friendica\Test\src\Util\Config;
use Friendica\App;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Core\Config\Cache\ConfigCacheLoader;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\VFSTrait;
use Friendica\Util\Config\ConfigFileLoader;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStream;
class ConfigCacheLoaderTest extends MockedTest
class ConfigFileLoaderTest extends MockedTest
{
use VFSTrait;
/**
* @var App\Mode|MockInterface
*/
private $mode;
protected function setUp()
{
parent::setUp();
$this->setUpVfsDir();
$this->mode = \Mockery::mock(App\Mode::class);
$this->mode->shouldReceive('isInstall')->andReturn(true);
}
/**
* Test the loadConfigFiles() method with default values
*/
public function testLoadConfigFiles()
{
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configFileLoader->setupCache($configCache);
$this->assertEquals($this->root->url(), $configCache->get('system', 'basepath'));
}
/**
@ -32,10 +55,10 @@ class ConfigCacheLoaderTest extends MockedTest
->at($this->root->getChild('config'))
->setContent('<?php return true;');
$configCacheLoader = new ConfigCacheLoader($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configCacheLoader->loadConfigFiles($configCache);
$configFileLoader->setupCache($configCache);
}
/**
@ -46,7 +69,6 @@ class ConfigCacheLoaderTest extends MockedTest
$this->delConfigFile('local.config.php');
$file = dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
@ -57,10 +79,10 @@ class ConfigCacheLoaderTest extends MockedTest
->at($this->root->getChild('config'))
->setContent(file_get_contents($file));
$configCacheLoader = new ConfigCacheLoader($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configCacheLoader->loadConfigFiles($configCache);
$configFileLoader->setupCache($configCache);
$this->assertEquals('testhost', $configCache->get('database', 'hostname'));
$this->assertEquals('testuser', $configCache->get('database', 'username'));
@ -79,7 +101,6 @@ class ConfigCacheLoaderTest extends MockedTest
$this->delConfigFile('local.config.php');
$file = dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
@ -90,10 +111,10 @@ class ConfigCacheLoaderTest extends MockedTest
->at($this->root->getChild('config'))
->setContent(file_get_contents($file));
$configCacheLoader = new ConfigCacheLoader($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configCacheLoader->loadConfigFiles($configCache);
$configFileLoader->setupCache($configCache);
$this->assertEquals('testhost', $configCache->get('database', 'hostname'));
$this->assertEquals('testuser', $configCache->get('database', 'username'));
@ -111,21 +132,20 @@ class ConfigCacheLoaderTest extends MockedTest
$this->delConfigFile('local.config.php');
$file = dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
'config' . DIRECTORY_SEPARATOR .
'.htconfig.test.php';
'.htconfig.php';
vfsStream::newFile('.htconfig.php')
->at($this->root)
->setContent(file_get_contents($file));
$configCacheLoader = new ConfigCacheLoader($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configCacheLoader->loadConfigFiles($configCache);
$configFileLoader->setupCache($configCache);
$this->assertEquals('testhost', $configCache->get('database', 'hostname'));
$this->assertEquals('testuser', $configCache->get('database', 'username'));
@ -137,7 +157,7 @@ class ConfigCacheLoaderTest extends MockedTest
$this->assertEquals('Europe/Berlin', $configCache->get('system', 'default_timezone'));
$this->assertEquals('fr', $configCache->get('system', 'language'));
$this->assertEquals('admin@friendica.local', $configCache->get('config', 'admin_email'));
$this->assertEquals('admin@test.it', $configCache->get('config', 'admin_email'));
$this->assertEquals('Friendly admin', $configCache->get('config', 'admin_nickname'));
$this->assertEquals('/another/php', $configCache->get('config', 'php_path'));
@ -161,7 +181,6 @@ class ConfigCacheLoaderTest extends MockedTest
vfsStream::create($structure, $this->root);
$file = dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
@ -172,9 +191,9 @@ class ConfigCacheLoaderTest extends MockedTest
->at($this->root->getChild('addon')->getChild('test')->getChild('config'))
->setContent(file_get_contents($file));
$configCacheLoader = new ConfigCacheLoader($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$conf = $configCacheLoader->loadAddonConfig('test');
$conf = $configFileLoader->loadAddonConfig('test');
$this->assertEquals('testhost', $conf['database']['hostname']);
$this->assertEquals('testuser', $conf['database']['username']);

View File

@ -0,0 +1,189 @@
<?php
namespace Friendica\Test\src\Util\Config;
use Friendica\App;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\VFSTrait;
use Friendica\Util\Config\ConfigFileLoader;
use Friendica\Util\Config\ConfigFileSaver;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStream;
class ConfigFileSaverTest extends MockedTest
{
use VFSTrait;
/**
* @var App\Mode|MockInterface
*/
private $mode;
protected function setUp()
{
parent::setUp();
$this->setUpVfsDir();
$this->mode = \Mockery::mock(App\Mode::class);
$this->mode->shouldReceive('isInstall')->andReturn(true);
}
public function dataConfigFiles()
{
return [
'config' => [
'fileName' => 'local.config.php',
'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
'config',
'relativePath' => 'config',
],
'ini' => [
'fileName' => 'local.ini.php',
'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
'config',
'relativePath' => 'config',
],
'htconfig' => [
'fileName' => '.htconfig.php',
'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'..' . DIRECTORY_SEPARATOR .
'datasets' . DIRECTORY_SEPARATOR .
'config',
'relativePath' => '',
],
];
}
/**
* Test the saveToConfigFile() method
* @dataProvider dataConfigFiles
*
* @todo 20190324 [nupplaphil] for ini-configs, it isn't possible to use $ or ! inside values
*/
public function testSaveToConfig($fileName, $filePath, $relativePath)
{
$this->delConfigFile('local.config.php');
if (empty($relativePath)) {
$root = $this->root;
$relativeFullName = $fileName;
} else {
$root = $this->root->getChild($relativePath);
$relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName;
}
vfsStream::newFile($fileName)
->at($root)
->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName));
$configFileSaver = new ConfigFileSaver($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configFileLoader->setupCache($configCache);
$this->assertEquals('admin@test.it', $configCache->get('config', 'admin_email'));
$this->assertEquals('frio', $configCache->get('system', 'theme'));
$this->assertNull($configCache->get('config', 'test_val'));
$this->assertNull($configCache->get('system', 'test_val2'));
// update values (system and config value)
$configFileSaver->addConfigValue('config', 'admin_email', 'new@mail.it');
$configFileSaver->addConfigValue('system', 'theme', 'vier');
// insert values (system and config value)
$configFileSaver->addConfigValue('config', 'test_val', 'Testingwith@all.we can');
$configFileSaver->addConfigValue('system', 'test_val2', 'TestIt First');
// overwrite value
$configFileSaver->addConfigValue('system', 'test_val2', 'TestIt Now');
// save it
$this->assertTrue($configFileSaver->saveToConfigFile());
$newConfigCache = new ConfigCache();
$configFileLoader->setupCache($newConfigCache);
$this->assertEquals('new@mail.it', $newConfigCache->get('config', 'admin_email'));
$this->assertEquals('Testingwith@all.we can', $newConfigCache->get('config', 'test_val'));
$this->assertEquals('vier', $newConfigCache->get('system', 'theme'));
$this->assertEquals('TestIt Now', $newConfigCache->get('system', 'test_val2'));
$this->assertTrue($this->root->hasChild($relativeFullName));
$this->assertTrue($this->root->hasChild($relativeFullName . '.old'));
$this->assertFalse($this->root->hasChild($relativeFullName . '.tmp'));
$this->assertEquals(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName), file_get_contents($this->root->getChild($relativeFullName . '.old')->url()));
}
/**
* Test the saveToConfigFile() method without permissions
* @dataProvider dataConfigFiles
*/
public function testNoPermission($fileName, $filePath, $relativePath)
{
$this->delConfigFile('local.config.php');
if (empty($relativePath)) {
$root = $this->root;
$relativeFullName = $fileName;
} else {
$root = $this->root->getChild($relativePath);
$relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName;
}
$root->chmod(000);
vfsStream::newFile($fileName)
->at($root)
->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName));
$configFileSaver = new ConfigFileSaver($this->root->url());
$configFileSaver->addConfigValue('system', 'test_val2', 'TestIt Now');
// wrong mod, so return false if nothing to write
$this->assertFalse($configFileSaver->saveToConfigFile());
}
/**
* Test the saveToConfigFile() method with nothing to do
* @dataProvider dataConfigFiles
*/
public function testNothingToDo($fileName, $filePath, $relativePath)
{
$this->delConfigFile('local.config.php');
if (empty($relativePath)) {
$root = $this->root;
$relativeFullName = $fileName;
} else {
$root = $this->root->getChild($relativePath);
$relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName;
}
vfsStream::newFile($fileName)
->at($root)
->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName));
$configFileSaver = new ConfigFileSaver($this->root->url());
$configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode);
$configCache = new ConfigCache();
$configFileLoader->setupCache($configCache);
// save nothing
$this->assertTrue($configFileSaver->saveToConfigFile());
$this->assertTrue($this->root->hasChild($relativeFullName));
$this->assertFalse($this->root->hasChild($relativeFullName . '.old'));
$this->assertFalse($this->root->hasChild($relativeFullName . '.tmp'));
$this->assertEquals(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName), file_get_contents($this->root->getChild($relativeFullName)->url()));
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace Friendica\Test\src\Util\Logger;
use Friendica\Test\MockedTest;
use Friendica\Util\Introspection;
use Mockery\MockInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
abstract class AbstractLoggerTest extends MockedTest
{
use LoggerDataTrait;
const LOGLINE = '/.* \[.*\]: .* \{.*\"file\":\".*\".*,.*\"line\":\d*,.*\"function\":\".*\".*,.*\"uid\":\".*\".*}/';
const FILE = 'test';
const LINE = 666;
const FUNC = 'myfunction';
/**
* @var Introspection|MockInterface
*/
protected $introspection;
/**
* Returns the content of the current logger instance
*
* @return string
*/
abstract protected function getContent();
/**
* Returns the current logger instance
*
* @param string $level the default loglevel
*
* @return LoggerInterface
*/
abstract protected function getInstance($level = LogLevel::DEBUG);
protected function setUp()
{
parent::setUp();
$this->introspection = \Mockery::mock(Introspection::class);
$this->introspection->shouldReceive('getRecord')->andReturn([
'file' => self::FILE,
'line' => self::LINE,
'function' => self::FUNC
]);
}
public function assertLogline($string)
{
$this->assertRegExp(self::LOGLINE, $string);
}
public function assertLoglineNums($assertNum, $string)
{
$this->assertEquals($assertNum, preg_match_all(self::LOGLINE, $string));
}
/**
* Test if the logger works correctly
*/
public function testNormal()
{
$logger = $this->getInstance();
$logger->emergency('working!');
$logger->alert('working too!');
$logger->debug('and now?');
$logger->notice('message', ['an' => 'context']);
$text = $this->getContent();
$this->assertLogline($text);
$this->assertLoglineNums(4, $text);
}
/**
* Test if a log entry is correctly interpolated
*/
public function testPsrInterpolate()
{
$logger = $this->getInstance();
$logger->emergency('A {psr} test', ['psr' => 'working']);
$logger->alert('An {array} test', ['array' => ['it', 'is', 'working']]);
$text = $this->getContent();
$this->assertContains('A working test', $text);
$this->assertContains('An ["it","is","working"] test', $text);
}
/**
* Test if a log entry contains all necessary information
*/
public function testContainsInformation()
{
$logger = $this->getInstance();
$logger->emergency('A test');
$text = $this->getContent();
$this->assertContains('"file":"' . self::FILE . '"', $text);
$this->assertContains('"line":' . self::LINE, $text);
$this->assertContains('"function":"' . self::FUNC . '"', $text);
}
/**
* Test if the minimum level is working
*/
public function testMinimumLevel()
{
$logger = $this->getInstance(LogLevel::NOTICE);
$logger->emergency('working');
$logger->alert('working');
$logger->error('working');
$logger->warning('working');
$logger->notice('working');
$logger->info('not working');
$logger->debug('not working');
$text = $this->getContent();
$this->assertLoglineNums(5, $text);
}
/**
* Test with different logging data
* @dataProvider dataTests
*/
public function testDifferentTypes($function, $message, array $context)
{
$logger = $this->getInstance();
$logger->$function($message, $context);
$text = $this->getContent();
$this->assertLogline($text);
$this->assertContains(@json_encode($context), $text);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace Friendica\Test\src\Util\Logger;
trait LoggerDataTrait
{
public function dataTests()
{
return [
'emergency' => [
'function' => 'emergency',
'message' => 'test',
'context' => ['a' => 'context'],
],
'alert' => [
'function' => 'alert',
'message' => 'test {test}',
'context' => ['a' => 'context', 2 => 'so', 'test' => 'works'],
],
'critical' => [
'function' => 'critical',
'message' => 'test crit 2345',
'context' => ['a' => 'context', 'wit' => ['more', 'array']],
],
'error' => [
'function' => 'error',
'message' => 2.554,
'context' => [],
],
'warning' => [
'function' => 'warning',
'message' => 'test warn',
'context' => ['a' => 'context'],
],
'notice' => [
'function' => 'notice',
'message' => 2346,
'context' => ['a' => 'context'],
],
'info' => [
'function' => 'info',
'message' => null,
'context' => ['a' => 'context'],
],
'debug' => [
'function' => 'debug',
'message' => true,
'context' => ['a' => false],
],
];
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Friendica\Test\src\Util\Logger;
use Friendica\Test\MockedTest;
use Friendica\Util\Logger\ProfilerLogger;
use Friendica\Util\Profiler;
use Mockery\MockInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class ProfilerLoggerTest extends MockedTest
{
use LoggerDataTrait;
/**
* @var LoggerInterface|MockInterface
*/
private $logger;
/**
* @var Profiler|MockInterface
*/
private $profiler;
protected function setUp()
{
parent::setUp();
$this->logger = \Mockery::mock(LoggerInterface::class);
$this->profiler = \Mockery::mock(Profiler::class);
}
/**
* Test if the profiler is profiling data
* @dataProvider dataTests
*/
public function testProfiling($function, $message, array $context)
{
$logger = new ProfilerLogger($this->logger, $this->profiler);
$this->logger->shouldReceive($function)->with($message, $context)->once();
$this->profiler->shouldReceive('saveTimestamp')->with(\Mockery::any(), 'file', \Mockery::any())->once();
$logger->$function($message, $context);
}
/**
* Test the log() function
*/
public function testProfilingLog()
{
$logger = new ProfilerLogger($this->logger, $this->profiler);
$this->logger->shouldReceive('log')->with(LogLevel::WARNING, 'test', ['a' => 'context'])->once();
$this->profiler->shouldReceive('saveTimestamp')->with(\Mockery::any(), 'file', \Mockery::any())->once();
$logger->log(LogLevel::WARNING, 'test', ['a' => 'context']);
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace Friendica\Test\src\Util\Logger;
use Friendica\Test\Util\VFSTrait;
use Friendica\Util\Logger\StreamLogger;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamFile;
use Psr\Log\LogLevel;
class StreamLoggerTest extends AbstractLoggerTest
{
use VFSTrait;
/**
* @var StreamLogger
*/
private $logger;
/**
* @var vfsStreamFile
*/
private $logfile;
protected function setUp()
{
parent::setUp();
$this->setUpVfsDir();
}
/**
* {@@inheritdoc}
*/
protected function getInstance($level = LogLevel::DEBUG)
{
$this->logfile = vfsStream::newFile('friendica.log')
->at($this->root);
$this->logger = new StreamLogger('test', $this->logfile->url(), $this->introspection, $level);
return $this->logger;
}
/**
* {@inheritdoc}
*/
protected function getContent()
{
return $this->logfile->getContent();
}
/**
* Test if a stream is working
*/
public function testStream()
{
$logfile = vfsStream::newFile('friendica.log')
->at($this->root);
$filehandler = fopen($logfile->url(), 'ab');
$logger = new StreamLogger('test', $filehandler, $this->introspection);
$logger->emergency('working');
$text = $logfile->getContent();
$this->assertLogline($text);
}
/**
* Test if the close statement is working
*/
public function testClose()
{
$logfile = vfsStream::newFile('friendica.log')
->at($this->root);
$logger = new StreamLogger('test', $logfile->url(), $this->introspection);
$logger->emergency('working');
$logger->close();
// close doesn't affect
$logger->emergency('working too');
$text = $logfile->getContent();
$this->assertLoglineNums(2, $text);
}
/**
* Test when a file isn't set
* @expectedException \LogicException
* @expectedExceptionMessage Missing stream URL.
*/
public function testNoUrl()
{
$logger = new StreamLogger('test', '', $this->introspection);
$logger->emergency('not working');
}
/**
* Test when a file cannot be opened
* @expectedException \UnexpectedValueException
* @expectedExceptionMessageRegExp /The stream or file .* could not be opened: .* /
*/
public function testWrongUrl()
{
$logfile = vfsStream::newFile('friendica.log')
->at($this->root)->chmod(0);
$logger = new StreamLogger('test', $logfile->url(), $this->introspection);
$logger->emergency('not working');
}
/**
* Test when the directory cannot get created
* @expectedException \UnexpectedValueException
* @expectedExceptionMessageRegExp /Directory .* cannot get created: .* /
*/
public function testWrongDir()
{
$logger = new StreamLogger('test', '/a/wrong/directory/file.txt', $this->introspection);
$logger->emergency('not working');
}
/**
* Test when the minimum level is not valid
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The level ".*" is not valid./
*/
public function testWrongMinimumLevel()
{
$logger = new StreamLogger('test', 'file.text', $this->introspection, 'NOPE');
}
/**
* Test when the minimum level is not valid
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The level ".*" is not valid./
*/
public function testWrongLogLevel()
{
$logfile = vfsStream::newFile('friendica.log')
->at($this->root);
$logger = new StreamLogger('test', $logfile->url(), $this->introspection);
$logger->log('NOPE', 'a test');
}
/**
* Test when the file is null
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage A stream must either be a resource or a string.
*/
public function testWrongFile()
{
$logger = new StreamLogger('test', null, $this->introspection);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace Friendica\Test\src\Util\Logger;
use Friendica\Util\Logger\SyslogLogger;
use Psr\Log\LogLevel;
class SyslogLoggerTest extends AbstractLoggerTest
{
/**
* @var SyslogLoggerWrapper
*/
private $logger;
protected function setUp()
{
parent::setUp();
$this->introspection->shouldReceive('addClasses')->with([SyslogLogger::class]);
}
/**
* {@inheritdoc}
*/
protected function getContent()
{
return $this->logger->getContent();
}
/**
* {@inheritdoc}
*/
protected function getInstance($level = LogLevel::DEBUG)
{
$this->logger = new SyslogLoggerWrapper('test', $this->introspection, $level);
return $this->logger;
}
/**
* Test when the minimum level is not valid
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The level ".*" is not valid./
*/
public function testWrongMinimumLevel()
{
$logger = new SyslogLoggerWrapper('test', $this->introspection, 'NOPE');
}
/**
* Test when the minimum level is not valid
* @expectedException \InvalidArgumentException
* @expectedExceptionMessageRegExp /The level ".*" is not valid./
*/
public function testWrongLogLevel()
{
$logger = new SyslogLoggerWrapper('test', $this->introspection);
$logger->log('NOPE', 'a test');
}
/**
* Test when the logfacility is wrong (string)
* @expectedException \UnexpectedValueException
* @expectedExceptionMessageRegExp /Can\'t open syslog for ident ".*" and facility ".*": .* /
*/
public function testServerException()
{
$logger = new SyslogLoggerWrapper('test', $this->introspection, LogLevel::DEBUG, null, 'a string');
$logger->emergency('not working');
}
/**
* Test the close() method
*/
public function testClose()
{
$logger = new SyslogLoggerWrapper('test', $this->introspection);
$logger->emergency('test');
$logger->close();
// Reopened itself
$logger->emergency('test');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Friendica\Test\src\Util\Logger;
use Friendica\Util\Introspection;
use Friendica\Util\Logger\SyslogLogger;
use Psr\Log\LogLevel;
/**
* Wraps the SyslogLogger for replacing the syslog call with a string field.
*/
class SyslogLoggerWrapper extends SyslogLogger
{
private $content;
public function __construct($channel, Introspection $introspection, $level = LogLevel::NOTICE, $logOpts = LOG_PID, $logFacility = LOG_USER)
{
parent::__construct($channel, $introspection, $level, $logOpts, $logFacility);
$this->content = '';
}
/**
* Gets the content from the wrapped Syslog
*
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* {@inheritdoc}
*/
protected function syslogWrapper($level, $entry)
{
$this->content .= $entry . PHP_EOL;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Friendica\Test\src\Util\Logger;
use Friendica\Test\MockedTest;
use Friendica\Util\Logger\VoidLogger;
use Psr\Log\LogLevel;
class VoidLoggerTest extends MockedTest
{
use LoggerDataTrait;
/**
* Test if the profiler is profiling data
* @dataProvider dataTests
*/
public function testNormal($function, $message, array $context)
{
$logger = new VoidLogger();
$logger->$function($message, $context);
}
/**
* Test the log() function
*/
public function testProfilingLog()
{
$logger = new VoidLogger();
$logger->log(LogLevel::WARNING, 'test', ['a' => 'context']);
}
}

Some files were not shown because too many files have changed in this diff Show More