From f93256f920e2a533f9d16652a6d1acb5fe2fe278 Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Wed, 3 Nov 2021 16:49:01 +0100 Subject: [PATCH 01/21] Update tinymce to 5.10.1 --- composer.json | 2 +- composer.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 582a064f7a..a0aea96b34 100644 --- a/composer.json +++ b/composer.json @@ -129,7 +129,7 @@ "robrichards/xmlseclibs": "^3.1.1", "simplesamlphp/simplesamlphp": "^1.19.0", "simplesamlphp/twig-configurable-i18n": "~2.3.3", - "tinymce/tinymce": "5.9.*" + "tinymce/tinymce": "5.10.*" }, "require-dev": { "guzzlehttp/guzzle": "^6.5", diff --git a/composer.lock b/composer.lock index afed282afd..8d679d01d9 100644 --- a/composer.lock +++ b/composer.lock @@ -8495,16 +8495,16 @@ }, { "name": "tinymce/tinymce", - "version": "5.9.2", + "version": "5.10.1", "source": { "type": "git", "url": "https://github.com/tinymce/tinymce-dist.git", - "reference": "48c665ad12ba0e4d8068ba0784026c7488aa4746" + "reference": "23dbb5d218707805b74c9fe4f1e2525bcd21e689" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tinymce/tinymce-dist/zipball/48c665ad12ba0e4d8068ba0784026c7488aa4746", - "reference": "48c665ad12ba0e4d8068ba0784026c7488aa4746", + "url": "https://api.github.com/repos/tinymce/tinymce-dist/zipball/23dbb5d218707805b74c9fe4f1e2525bcd21e689", + "reference": "23dbb5d218707805b74c9fe4f1e2525bcd21e689", "shasum": "" }, "type": "component", @@ -8545,7 +8545,7 @@ "tinymce", "wysiwyg" ], - "time": "2021-09-08T03:45:09+00:00" + "time": "2021-11-03T03:57:12+00:00" }, { "name": "twig/extensions", From 16820b48f861bbe9806ee93b553feddc6a5cf52d Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 3 Nov 2021 19:58:24 +0100 Subject: [PATCH 02/21] Getting Grale/WebDAV PHP 8.0 compatible by using forked and fixed egroupware/guzzlehttp:3.9.3 --- composer.json | 3 +- composer.lock | 141 ++++++++++++++++++++++++++------------------------ 2 files changed, 73 insertions(+), 71 deletions(-) diff --git a/composer.json b/composer.json index a0aea96b34..3e6422e378 100644 --- a/composer.json +++ b/composer.json @@ -95,7 +95,6 @@ "egroupware/collabora": "self.version", "egroupware/compress": "^2.2.3", "egroupware/crypt": "^2.7.13", - "egroupware/guzzlestream": "dev-master", "egroupware/icalendar": "^2.1.9", "egroupware/imap-client": "2.30.4", "egroupware/magicsuggest": "^2.1", @@ -108,7 +107,7 @@ "egroupware/status": "self.version", "egroupware/swoolepush": "self.version", "egroupware/tracker": "self.version", - "egroupware/webdav": "^v0.3.1", + "egroupware/webdav": "^v0.3.2", "egroupware/z-push-dev": "^2.5", "giggsey/libphonenumber-for-php": "^8.12", "npm-asset/as-jqplot": "1.0.*", diff --git a/composer.lock b/composer.lock index 8d679d01d9..e3d2d0d48a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5a9ac5a19bb28961cbb8215f95ea72b9", + "content-hash": "7caa16a5ff78c32472d6d8bd4c61814b", "packages": [ { "name": "adldap2/adldap2", @@ -1092,18 +1092,80 @@ }, "time": "2017-11-11T00:00:00+00:00" }, + { + "name": "egroupware/guzzlehttp", + "version": "3.9.3", + "target-dir": "Guzzle/Http", + "source": { + "type": "git", + "url": "https://github.com/EGroupware/guzzlehttp.git", + "reference": "761951f0cb98109275cfd95776ab3725a903389c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EGroupware/guzzlehttp/zipball/761951f0cb98109275cfd95776ab3725a903389c", + "reference": "761951f0cb98109275cfd95776ab3725a903389c", + "shasum": "" + }, + "require": { + "egroupware/guzzlestream": "^3.9.3", + "guzzle/common": "^3.9.2", + "guzzle/parser": "^3.9.2", + "php": ">=5.3.2" + }, + "replace": { + "guzzle/http": "*" + }, + "suggest": { + "ext-curl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle\\Http": "" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "HTTP libraries used by Guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "client", + "curl", + "http", + "http client" + ], + "support": { + "source": "https://github.com/EGroupware/guzzlehttp/tree/3.9.3" + }, + "time": "2021-11-03T17:38:18+00:00" + }, { "name": "egroupware/guzzlestream", - "version": "dev-master", + "version": "3.9.3", "target-dir": "Guzzle/Stream", "source": { "type": "git", - "url": "https://github.com/EGroupware/stream.git", + "url": "https://github.com/EGroupware/guzzlestream.git", "reference": "d29fc35ebf3bd752308520aa5f17a3e5500f6af3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EGroupware/stream/zipball/d29fc35ebf3bd752308520aa5f17a3e5500f6af3", + "url": "https://api.github.com/repos/EGroupware/guzzlestream/zipball/d29fc35ebf3bd752308520aa5f17a3e5500f6af3", "reference": "d29fc35ebf3bd752308520aa5f17a3e5500f6af3", "shasum": "" }, @@ -1695,20 +1757,20 @@ }, { "name": "egroupware/webdav", - "version": "v0.3.1", + "version": "0.3.2", "source": { "type": "git", "url": "https://github.com/EGroupware/WebDAV.git", - "reference": "554b8ed3fb3bc98427f0c1edbba7f9bab9894d4c" + "reference": "639ea0b979ffb75107bcc7f8a37a74acc9410720" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/EGroupware/WebDAV/zipball/554b8ed3fb3bc98427f0c1edbba7f9bab9894d4c", - "reference": "554b8ed3fb3bc98427f0c1edbba7f9bab9894d4c", + "url": "https://api.github.com/repos/EGroupware/WebDAV/zipball/639ea0b979ffb75107bcc7f8a37a74acc9410720", + "reference": "639ea0b979ffb75107bcc7f8a37a74acc9410720", "shasum": "" }, "require": { - "guzzle/http": "~3.0", + "egroupware/guzzlehttp": "^3.9.3", "php": ">=5.3.0", "psr/log": "~1.0" }, @@ -1743,7 +1805,7 @@ "stream", "wrapper" ], - "time": "2021-09-10T11:59:20+00:00" + "time": "2021-11-03T18:16:48+00:00" }, { "name": "egroupware/z-push-dev", @@ -2106,64 +2168,6 @@ "abandoned": "guzzle/guzzle", "time": "2014-08-11T04:32:36+00:00" }, - { - "name": "guzzle/http", - "version": "v3.9.2", - "target-dir": "Guzzle/Http", - "source": { - "type": "git", - "url": "https://github.com/Guzzle3/http.git", - "reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Guzzle3/http/zipball/1e8dd1e2ba9dc42332396f39fbfab950b2301dc5", - "reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5", - "shasum": "" - }, - "require": { - "guzzle/common": "self.version", - "guzzle/parser": "self.version", - "guzzle/stream": "self.version", - "php": ">=5.3.2" - }, - "suggest": { - "ext-curl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.7-dev" - } - }, - "autoload": { - "psr-0": { - "Guzzle\\Http": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "HTTP libraries used by Guzzle", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "client", - "curl", - "http", - "http client" - ], - "abandoned": "guzzle/guzzle", - "time": "2014-08-11T04:32:36+00:00" - }, { "name": "guzzle/parser", "version": "v3.9.2", @@ -10731,7 +10735,6 @@ "egroupware/adodb-php": 20, "egroupware/bookmarks": 20, "egroupware/collabora": 20, - "egroupware/guzzlestream": 20, "egroupware/news_admin": 20, "egroupware/openid": 20, "egroupware/projectmanager": 20, From 49b2313cfd0eb25dd03587d44976bd246552c84e Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 4 Nov 2021 09:45:55 +0100 Subject: [PATCH 03/21] Fix error (TypeError): count(): Argument #1 ($value) must be of type Countable|array, null given --- mail/inc/class.mail_compose.inc.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mail/inc/class.mail_compose.inc.php b/mail/inc/class.mail_compose.inc.php index af7d42e33d..052d9f5bdf 100644 --- a/mail/inc/class.mail_compose.inc.php +++ b/mail/inc/class.mail_compose.inc.php @@ -2347,8 +2347,8 @@ class mail_compose @htmlspecialchars(lang("date").": ".Mail::_strtotime($headers['DATE'],'r',true), ENT_QUOTES | ENT_IGNORE,Mail::$displayCharset, false)."\r\n". '-------------------------------------------------'."\r\n \r\n "; $this->sessionData['mimeType'] = 'plain'; - - for($i=0; $i0) { $this->sessionData['body'] .= "
"; } From 1f2833670291942249ae8878fd0fa39d93b866ec Mon Sep 17 00:00:00 2001 From: Hadi Nategh Date: Thu, 4 Nov 2021 14:12:21 +0100 Subject: [PATCH 04/21] Fix stripos(): Argument #3 ($offset) must be contained in argument #1 ($haystack) by preventing empty html value being processed --- api/src/Mail/Html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/Mail/Html.php b/api/src/Mail/Html.php index 667b845e4f..470e871ae6 100644 --- a/api/src/Mail/Html.php +++ b/api/src/Mail/Html.php @@ -555,7 +555,7 @@ class Html $searchFor = '
';
 			$pos = stripos($html,$searchFor);
 		}
-		if ($pos === false)
+		if ($pos === false || !$html)
 		{
 			return $html;
 		}

From 62fcc3156ca5154510ffc403d9e43409436cfe94 Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Thu, 4 Nov 2021 14:30:23 +0100
Subject: [PATCH 05/21] Fix PHP8.0 (TypeError): explode(): Argument #2
 ($string) must be of type string, array given

---
 api/src/Framework/Ajax.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/src/Framework/Ajax.php b/api/src/Framework/Ajax.php
index e10618fa2c..518851c407 100755
--- a/api/src/Framework/Ajax.php
+++ b/api/src/Framework/Ajax.php
@@ -998,7 +998,7 @@ abstract class Ajax extends Api\Framework
 		{
 			$open_tabs = $GLOBALS['egw_info']['user']['preferences']['common']['open_tabs'];
 		}
-		$open_tabs = $open_tabs ? explode(',',$open_tabs) : array();
+		$open_tabs = $open_tabs && is_string($open_tabs) ? explode(',',$open_tabs) : (array)$open_tabs;
 		if ($active_tab && !in_array($active_tab,$open_tabs))
 		{
 			$open_tabs[] = $active_tab;

From dd42733393f7ef97e2006dc130f25dd3cbd2fe6c Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Thu, 4 Nov 2021 15:50:48 +0100
Subject: [PATCH 06/21] Fix PHP8.0 (TypeError): array_diff(): Argument #2 must
 be of type array, null given

---
 admin/inc/class.admin_acl.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/admin/inc/class.admin_acl.inc.php b/admin/inc/class.admin_acl.inc.php
index b66175563b..5d40e2515d 100644
--- a/admin/inc/class.admin_acl.inc.php
+++ b/admin/inc/class.admin_acl.inc.php
@@ -193,7 +193,7 @@ class admin_acl
 							}
 							else
 							{
-								$check = array_diff($memberships, $groups);
+								$check = array_diff($memberships, (array)$groups);
 								//error_log(__METHOD__."() app=$app, array_diff(memberships=".array2string($memberships).", groups=".array2string($groups).")=".array2string($check));
 								if (!$check) continue;	// would give sql error otherwise!
 							}

From 201d1f9972d0426b37c07708a9543986ce8f4b2d Mon Sep 17 00:00:00 2001
From: Hadi Nategh 
Date: Thu, 4 Nov 2021 17:23:45 +0100
Subject: [PATCH 07/21] Fix PHP8.0 error (TypeError): strlen(): Argument #1
 ($string) must be of type string, array given

---
 api/src/Storage/Base.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/src/Storage/Base.php b/api/src/Storage/Base.php
index 655d7dd965..3fa3d70d27 100644
--- a/api/src/Storage/Base.php
+++ b/api/src/Storage/Base.php
@@ -632,7 +632,7 @@ class Base
 					{
 						continue;	// no need to write that (unset) column
 					}
-					if ($this->table_def['fd'][$db_col]['type'] == 'varchar' &&
+					if ($this->table_def['fd'][$db_col]['type'] == 'varchar' && is_string($this->data[$col]) &&
 						strlen($this->data[$col]) > $this->table_def['fd'][$db_col]['precision'])
 					{
 						// truncate the field to mamimum length, if upper layers didn't care

From 4db49da7e563565ffad9450d3adfbd2cb0837e1b Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 09:07:58 +0100
Subject: [PATCH 08/21] fix PHP 8.0 error: trim expects string, array given
 removed trim

---
 api/src/Contacts/Ldap.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/src/Contacts/Ldap.php b/api/src/Contacts/Ldap.php
index dadc7bb021..de8d925e2e 100644
--- a/api/src/Contacts/Ldap.php
+++ b/api/src/Contacts/Ldap.php
@@ -594,7 +594,7 @@ class Ldap
 				{
 					// dont convert the (binary) jpegPhoto!
 					$ldapContact[$ldapFieldName] = $ldapFieldName == 'jpegphoto' ? $data[$egwFieldName] :
-						Api\Translation::convert(trim($data[$egwFieldName]),$this->charset,'utf-8');
+						Api\Translation::convert($data[$egwFieldName], $this->charset,'utf-8');
 				}
 				elseif($isUpdate && isset($data[$egwFieldName]))
 				{

From b3116e09e5d9455f9acd0426c920643472ff66c6 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 11:09:27 +0100
Subject: [PATCH 09/21] fix PHP 8.0 error: Undefined constant "filter"

---
 calendar/inc/class.calendar_so.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/calendar/inc/class.calendar_so.inc.php b/calendar/inc/class.calendar_so.inc.php
index c97bc7573a..c38081cd7c 100644
--- a/calendar/inc/class.calendar_so.inc.php
+++ b/calendar/inc/class.calendar_so.inc.php
@@ -2844,7 +2844,7 @@ ORDER BY cal_user_type, cal_usre_id
 						$days[$locts]= $locts;
 					}
 				}
-				elseif (($filter == 'map' || filter == 'tz_map') &&
+				elseif (($filter == 'map' || $filter == 'tz_map') &&
 						!$tz_exception)
 				{
 					// no pseudo exception date

From 7ac13da96fc473c772766ed3da2333a4c2999f2f Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 11:22:49 +0100
Subject: [PATCH 10/21] fix PHP 8.0 error: calendar_boupdate::update():
 Argument #6 ($messages) cannot be passed by reference

---
 calendar/inc/class.calendar_uiforms.inc.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php
index bedef318c8..0a6cadda87 100644
--- a/calendar/inc/class.calendar_uiforms.inc.php
+++ b/calendar/inc/class.calendar_uiforms.inc.php
@@ -984,7 +984,8 @@ class calendar_uiforms extends calendar_ui
 							$exception['recurrence'] += $offset;
 							$exception['reference'] = $event['id'];
 							$exception['uid'] = $event['uid'];
-							$this->bo->update($exception, true, true, true, true, $msg=null, $content['no_notifications']);
+							$msg = null;
+							$this->bo->update($exception, true, true, true, true, $msg, $content['no_notifications']);
 						}
 					}
 				}

From 57ac760a78f4084de8862166094c8cc06f7cd50c Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 11:38:49 +0100
Subject: [PATCH 11/21] fix PHP 8.0 ValueError: stripos(): Argument #3
 ($offset) must be contained in argument #1 ($haystack)

---
 api/src/Mail/Html.php | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/api/src/Mail/Html.php b/api/src/Mail/Html.php
index 470e871ae6..b8b55e08cf 100644
--- a/api/src/Mail/Html.php
+++ b/api/src/Mail/Html.php
@@ -564,7 +564,11 @@ class Html
 		{
 			// avoid infinit loop in case the endof pre can't be found, just give the
 			// end position to return the rest of content as return html
-			$endofpre = (stripos($html,'
',$pos) === false ? strlen($html) : stripos($html,'',$pos)); + if (($endofpre = stripos($html, '', $pos)) === false) + { + $html2ret[] = substr($html, $pos); + break; + } $length = $endofpre-$pos+6; $html2ret[] = substr($html,$pos,$length); $searchFor = '
Date: Fri, 5 Nov 2021 12:33:20 +0100
Subject: [PATCH 12/21] fix PHP 8.0 error in SMime by updating to our fixed
 Horde/Crypt:2.7.14

---
 composer.json |  2 +-
 composer.lock | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/composer.json b/composer.json
index 3e6422e378..c642eac199 100644
--- a/composer.json
+++ b/composer.json
@@ -94,7 +94,7 @@
 		"egroupware/bookmarks": "self.version",
 		"egroupware/collabora": "self.version",
 		"egroupware/compress": "^2.2.3",
-		"egroupware/crypt": "^2.7.13",
+		"egroupware/crypt": "^2.7.14",
 		"egroupware/icalendar": "^2.1.9",
 		"egroupware/imap-client": "2.30.4",
 		"egroupware/magicsuggest": "^2.1",
diff --git a/composer.lock b/composer.lock
index e3d2d0d48a..221772c95d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7caa16a5ff78c32472d6d8bd4c61814b",
+    "content-hash": "de144b0b6d74a96e1d0fa87a0ece2b7c",
     "packages": [
         {
             "name": "adldap2/adldap2",
@@ -1032,16 +1032,16 @@
         },
         {
             "name": "egroupware/crypt",
-            "version": "2.7.13",
+            "version": "2.7.14",
             "source": {
                 "type": "git",
                 "url": "https://github.com/EGroupware/Crypt.git",
-                "reference": "d25af6514c134426647dae2c47e5bf07bb7c4d1e"
+                "reference": "ae2d6bfc81b1c820183c355ae100f60d3133f47e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/EGroupware/Crypt/zipball/d25af6514c134426647dae2c47e5bf07bb7c4d1e",
-                "reference": "d25af6514c134426647dae2c47e5bf07bb7c4d1e",
+                "url": "https://api.github.com/repos/EGroupware/Crypt/zipball/ae2d6bfc81b1c820183c355ae100f60d3133f47e",
+                "reference": "ae2d6bfc81b1c820183c355ae100f60d3133f47e",
                 "shasum": ""
             },
             "require": {
@@ -1088,7 +1088,7 @@
             "description": "Cryptography library",
             "homepage": "https://www.horde.org/libraries/Horde_Crypt",
             "support": {
-                "source": "https://github.com/EGroupware/Crypt/tree/v2.7.13"
+                "source": "https://github.com/EGroupware/Crypt/tree/2.7.14"
             },
             "time": "2017-11-11T00:00:00+00:00"
         },

From 42d45131e61e5308b097a5d3b5de20e42e015c14 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 13:52:20 +0100
Subject: [PATCH 13/21] fix PHP 8.0 TypeError: Unsupported operand types:
 EGroupware\Api\DateTime - int

---
 calendar/inc/class.calendar_boupdate.inc.php | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php
index b64b0cd269..0b7280c873 100644
--- a/calendar/inc/class.calendar_boupdate.inc.php
+++ b/calendar/inc/class.calendar_boupdate.inc.php
@@ -1550,7 +1550,7 @@ class calendar_boupdate extends calendar_bo
 			$event['created'] = $save_event['created'] = $this->now;
 			$event['creator'] = $save_event['creator'] = $this->user;
 		}
-		$set_recurrences = $old_event ? abs($event['recur_enddate'] - $old_event['recur_enddate']) > 1 : false;
+		$set_recurrences = $old_event ? abs(Api\DateTime::to($event['recur_enddate'], 'utc') - Api\DateTime::to($old_event['recur_enddate'], 'utc')) > 1 : false;
 		$set_recurrences_start = 0;
 		if (($cal_id = $this->so->save($event,$set_recurrences,$set_recurrences_start,0,$event['etag'])) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE)
 		{
@@ -1945,7 +1945,8 @@ class calendar_boupdate extends calendar_bo
 						if (!($exception = $this->read($id))) continue;
 						$exception['uid'] = Api\CalDAV::generate_uid('calendar', $id);
 						$exception['reference'] = $exception['recurrence'] = 0;
-						$this->update($exception, true, true, false, true, $msg=null, true);
+						$msg = null;
+						$this->update($exception, true, true, false, true, $msg, true);
 						++$exceptions_kept;
 					}
 				}

From 108d077ccdc77a04737b76af24c257c59e4cb2af Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 14:23:03 +0100
Subject: [PATCH 14/21] fix PHP 8.0 TypeError: json_decode(): Argument #1
 ($json) must be of type string, array given

---
 calendar/inc/class.calendar_uiforms.inc.php | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php
index 0a6cadda87..9f5359cb66 100644
--- a/calendar/inc/class.calendar_uiforms.inc.php
+++ b/calendar/inc/class.calendar_uiforms.inc.php
@@ -1496,9 +1496,6 @@ class calendar_uiforms extends calendar_ui
 	 */
 	public function ajax_conflicts()
 	{
-		$participants = json_decode($_GET['participants'],true);
-		unset($_GET['participants']);
-
 		$content = $this->default_add_event();
 
 		// Process edit wants to see input values

From de7f4329ffde25d61081c316a1bf8127d5322fc1 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 14:37:21 +0100
Subject: [PATCH 15/21] fix PHP 8.0 TypeError: uasort(): Argument #1 ($array)
 must be of type array, null given

---
 api/src/Mail.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/api/src/Mail.php b/api/src/Mail.php
index cee470c3aa..0f23660050 100644
--- a/api/src/Mail.php
+++ b/api/src/Mail.php
@@ -3067,7 +3067,7 @@ class Mail
 				$folders = $this->icServer->getMailboxes($path, $_search, true);
 			}
 
-			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
+			if (is_array($folders)) uasort($folders, array($this,'sortByMailbox'));
 		}
 		elseif(!$_nodePath) // all
 		{
@@ -3088,7 +3088,7 @@ class Mail
 			if (self::$debugTimes) $starttime = microtime (true);
 			// Merge of all auto folders and specialusefolders
 			$autoFoldersTmp = array_unique((array_merge(self::$autoFolders, array_values(self::$specialUseFolders))));
-			uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
+			if (is_array($folders)) uasort($folders,array($this,'sortByMailbox'));//ksort($folders);
 			$tmpFolders = $folders;
 			$inboxFolderObject=$inboxSubFolderObjects=$autoFolderObjects=$typeFolderObject=$mySpecialUseFolders=array();
 			$googleMailFolderObject=$googleAutoFolderObjects=$googleSubFolderObjects=array();

From 66d45452abd0dc0cb5b0afe75175dc8f57d056ca Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 14:43:32 +0100
Subject: [PATCH 16/21] fix PHP 8.0 TypeError: explode(): Argument #2 ($string)
 must be of type string, array given

---
 addressbook/inc/class.addressbook_groupdav.inc.php | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php
index 95a7d5ad86..4114bf9a5e 100644
--- a/addressbook/inc/class.addressbook_groupdav.inc.php
+++ b/addressbook/inc/class.addressbook_groupdav.inc.php
@@ -98,8 +98,11 @@ class addressbook_groupdav extends Api\CalDAV\Handler
 		}
 		if ($this->debug) error_log(__METHOD__."() contact_repository={$this->bo->contact_repository}, account_repository={$this->bo->account_repository}, REQUEST_URI=$_SERVER[REQUEST_URI] --> path_attr=".self::$path_attr.", path_extension=".self::$path_extension);
 
-		$this->home_set_pref = $GLOBALS['egw_info']['user']['preferences']['groupdav']['addressbook-home-set'];
-		$this->home_set_pref = $this->home_set_pref ? explode(',',$this->home_set_pref) : array();
+		$this->home_set_pref = $GLOBALS['egw_info']['user']['preferences']['groupdav']['addressbook-home-set'] ?? [];
+		if (!is_array($this->home_set_pref))
+		{
+			$this->home_set_pref = $this->home_set_pref ? explode(',', $this->home_set_pref) : array();
+		}
 
 		// silently switch "Sync all into one" preference on for OS X addressbook, as it only supports one AB
 		// this restores behavior before Lion (10.7), where AB synced all ABs contained in addressbook-home-set

From 9fbd07e3358cb93d3935978519a4c865d43df2db Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 14:49:56 +0100
Subject: [PATCH 17/21] fix PHP 8.0 TypeError: Unsupported operand types: null
 + array

---
 api/src/Etemplate/Widget/File.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/src/Etemplate/Widget/File.php b/api/src/Etemplate/Widget/File.php
index 06caef931a..add9a199e9 100644
--- a/api/src/Etemplate/Widget/File.php
+++ b/api/src/Etemplate/Widget/File.php
@@ -41,7 +41,7 @@ class File extends Etemplate\Widget
 	 */
 	public function __construct($xml='')
 	{
-		$this->bool_attr_default += array(
+		$this->bool_attr_default = ($this->bool_attr_default ?? []) + array(
 			'multiple' => false,
 		);
 

From 6b8f8c64730aa04130802d89c3b05155a6046b30 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 14:58:38 +0100
Subject: [PATCH 18/21] fix PHP 8.0 TypeError: Unsupported operand types:
 EGroupware\Api\DateTime - int

---
 calendar/inc/class.calendar_ical.inc.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php
index 7ff64226c3..c026b4ffc0 100644
--- a/calendar/inc/class.calendar_ical.inc.php
+++ b/calendar/inc/class.calendar_ical.inc.php
@@ -928,7 +928,7 @@ class calendar_ical extends calendar_boupdate
 
 				if ($alarmData['offset'])
 				{
-					$alarmData['time'] = $event['start'] - $alarmData['offset'];
+					$alarmData['time'] = Api\DateTime::to($event['start'], 'ts') - $alarmData['offset'];
 				}
 
 				$description = trim(preg_replace("/\r?\n?\\[[A-Z_]+:.*\\]/i", '', $event['description']));

From a50054b8c75543bceb7866ad98813365bf2553b3 Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 15:12:40 +0100
Subject: [PATCH 19/21] fix PHP 8.0 TypeError: count(): Argument #1 ($value)
 must be of type Countable|array, null given

---
 mail/inc/class.mail_ui.inc.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/mail/inc/class.mail_ui.inc.php b/mail/inc/class.mail_ui.inc.php
index b039389b42..08dda2556f 100644
--- a/mail/inc/class.mail_ui.inc.php
+++ b/mail/inc/class.mail_ui.inc.php
@@ -2291,8 +2291,8 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
 		$content['mailDisplayBodySrc'] = Egw::link('/index.php',$linkData);
 		$content['mail_displayattachments'] = $attachmentHTMLBlock;
 		$content['mail_id']=$rowID;
-		$content['mailDisplayContainerClass']=(count($attachments)?"mailDisplayContainer mailDisplayContainerFixedHeight":"mailDisplayContainer mailDisplayContainerFullHeight");
-		$content['mailDisplayAttachmentsClass']=(count($attachments)?"mailDisplayAttachments":"mail_DisplayNone");
+		$content['mailDisplayContainerClass']=!empty($attachments)?"mailDisplayContainer mailDisplayContainerFixedHeight":"mailDisplayContainer mailDisplayContainerFullHeight";
+		$content['mailDisplayAttachmentsClass']=!empty($attachments)?"mailDisplayAttachments":"mail_DisplayNone";
 
 		// DRAG attachments actions
 		$etpl->setElementAttribute('mail_displayattachments', 'actions', array(

From 2f1559a4902bc09d57f38a4bb9b8baa408d7deaf Mon Sep 17 00:00:00 2001
From: Ralf Becker 
Date: Fri, 5 Nov 2021 15:18:57 +0100
Subject: [PATCH 20/21] fix PHP 8.0 TypeError: count(): Argument #1 ($value)
 must be of type Countable|array, null given

---
 api/src/Mail.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/src/Mail.php b/api/src/Mail.php
index 0f23660050..5642fba221 100644
--- a/api/src/Mail.php
+++ b/api/src/Mail.php
@@ -5062,7 +5062,7 @@ class Mail
 	static function &getdisplayableBody(&$mailClass, $bodyParts, $preserveHTML = false,  $useTidy = true)
 	{
 		$message='';
-		for($i=0, $cnt=count($bodyParts); $i < $cnt; $i++)
+		for($i=0, $cnt=count($bodyParts ?? []); $i < $cnt; $i++)
 		{
 			if (!isset($bodyParts[$i]['body'])) {
 				$bodyParts[$i]['body'] = self::getdisplayableBody($mailClass, $bodyParts[$i], $preserveHTML, $useTidy);

From b125e1b2fd680c69c7d80e2c48fefaa7a4241d35 Mon Sep 17 00:00:00 2001
From: nathan 
Date: Fri, 5 Nov 2021 10:44:44 -0600
Subject: [PATCH 21/21] Infolog: Fix linking a second project will remove one
 project after a second save

With test.
---
 infolog/inc/class.infolog_ui.inc.php |   2 +-
 infolog/tests/DoubleLinkPMTest.php   | 312 +++++++++++++++++++++++++++
 2 files changed, 313 insertions(+), 1 deletion(-)
 create mode 100644 infolog/tests/DoubleLinkPMTest.php

diff --git a/infolog/inc/class.infolog_ui.inc.php b/infolog/inc/class.infolog_ui.inc.php
index bdeeb802a3..ffc868f5d1 100644
--- a/infolog/inc/class.infolog_ui.inc.php
+++ b/infolog/inc/class.infolog_ui.inc.php
@@ -2220,7 +2220,7 @@ class infolog_ui
 
 		$content['info_anz_subs'] = (int)$content['info_anz_subs'];	// gives javascript error if empty!
 
-		$old_pm_id = is_array($pm_links) ? array_shift($pm_links) : $content['old_pm_id'];
+		$old_pm_id = $content['pm_id'] ?: (is_array($pm_links) ? array_shift($pm_links) : $content['old_pm_id']);
 		unset($content['old_pm_id']);
 
 		if ($info_id && $this->bo->history)
diff --git a/infolog/tests/DoubleLinkPMTest.php b/infolog/tests/DoubleLinkPMTest.php
new file mode 100644
index 0000000000..a548a31317
--- /dev/null
+++ b/infolog/tests/DoubleLinkPMTest.php
@@ -0,0 +1,312 @@
+ui = new \infolog_ui();
+
+		$this->ui->tmpl = $this->createPartialMock(Etemplate::class, array('exec'));
+		$this->ui->tmpl->expects($this->any())
+					   ->method('exec')
+					   ->will($this->returnCallback([$this, 'mockExec']));
+
+		$this->bo = $this->ui->bo;
+		$this->pm_bo = new \projectmanager_bo();
+
+		$this->bo->tracking = $this->createStub(\infolog_tracking::class);
+		$this->bo->tracking->method('track')->willReturn(0);
+
+		// Make sure projects are not there first
+		$pm_numbers = array(
+			'TEST 1',
+			'TEST 2'
+		);
+		foreach($pm_numbers as $number)
+		{
+			$project = $this->pm_bo->read(array('pm_number' => $number));
+			if($project && $project['pm_id'])
+			{
+				$this->pm_bo->delete($project);
+			}
+		}
+
+		$this->makeProject("1");
+
+		// Make another project, we need 2
+		$this->pm_bo->data = array();
+		$this->makeProject("2");
+	}
+
+	protected function tearDown() : void
+	{
+		// Remove infolog under test
+		if($this->info_id)
+		{
+			$this->bo->delete($this->info_id, False, False, True);
+			// One more time for history
+			$this->bo->delete($this->info_id, False, False, True);
+		}
+
+		// Remove the test project
+		$this->deleteProject();
+
+		$this->bo = null;
+		$this->pm_bo = null;
+
+		parent::tearDown();
+	}
+
+
+	/**
+	 * Have an infolog that is part of a project, but then add it into another project.
+	 * We expect both projects to stay linked, but _the_ project to be unchanged.
+	 * It works correctly if you debug it but not if you run it
+	 */
+	public function testInProjectLinkAnother()
+	{
+		$first_project = $this->pm_id[0];
+		$second_project = $this->pm_id[1];
+
+		$info = $this->getTestInfolog();
+		// Set project by ID
+		//$info['pm_id'] = $first_project;
+
+		$this->info_id = $this->bo->write($info);
+		$this->assertIsInt($this->info_id);
+		$this->assertGreaterThan(0, $this->info_id);
+
+		// Force links to run notification now so we get valid testing - it
+		// usually waits until Egw::on_shutdown();
+		Link::run_notifies();
+
+		// Fake opening the edit dialog, important not to pass an array to accurately copy normal behaviour
+		$this->ui->edit($this->info_id);
+		// Set button 'apply' to save, but not try to close the window since
+		// that would fail
+		$content = self::$mocked_exec_result;
+
+		$content['pm_id'] = $first_project;
+		$content['button'] = array('apply' => true);
+		$this->ui->edit($content);
+
+		// Force links to run notification now so we get valid testing - it
+		// usually waits until Egw::on_shutdown();
+		Link::run_notifies();
+		// Now load it again
+		$info = $this->bo->read($this->info_id);
+
+		// Check original pm_id is there
+		$this->assertNotNull($info['pm_id'], 'Project was not set');
+		$this->assertEquals($first_project, $info['pm_id'], 'Project went missing');
+
+		// Now link another project
+		Link::link('infolog', $this->info_id, 'projectmanager', $second_project, "This is the second project");
+
+		// Force links to run notification now so we get valid testing - it
+		// usually waits until Egw::on_shutdown();
+		Link::run_notifies();
+		$this->check_links($this->info_id, $first_project, $second_project);
+
+		// Make a call to edit, looks like user updated subject and clicked Apply
+		// Ticket #63114 says an edit will remove the second project
+		$this->loadAndCheckLinks($info, $first_project, $second_project);
+
+		// Check infolog is in original project
+		$this->checkElements($first_project);
+		// Check infolog is in second project
+		$this->checkElements($second_project);
+	}
+
+	public function mockExec($method, $content, $sel_options, $readonlys, $preserve)
+	{
+		$tmpl = new Etemplate('infolog.edit');
+		$GLOBALS['egw']->categories = new Categories('', 'infolog');
+		$result = parent::mockedExec($tmpl, $content, $sel_options, $readonlys, $preserve);
+		// Check for the load
+		$data = array();
+		foreach($result as $command)
+		{
+			if($command['type'] == 'et2_load')
+			{
+				$data = $command['data'];
+				break;
+			}
+		}
+
+		Etemplate::ajax_process_content($data['data']['etemplate_exec_id'], $data['data']['content'], true);
+
+		$content = static::$mocked_exec_result;
+
+		return $content;
+	}
+
+	/**
+	 * Load infolog via etemplate & check links & pm_id are still there
+	 *
+	 * @param $pm_id_1
+	 * @param $pm_id_2
+	 */
+	protected function loadAndCheckLinks($info, $pm_id_1, $pm_id_2)
+	{
+		// Fake opening the edit dialog, important not to pass an array to accurately copy normal behaviour
+		$this->ui->edit($info['info_id']);
+
+		// Set button 'apply' to save, but not try to close the window since
+		// that would fail
+		$content = self::$mocked_exec_result;
+
+		$content['info_subject'] .= " save #1";
+		$content['button'] = array('apply' => true);
+		$this->ui->edit($content);
+
+		// Now do it again
+		// Ticket #63114 says a second edit will remove the second project
+		$content = self::$mocked_exec_result;
+
+		$content['info_subject'] .= " save #2";
+		$content['button'] = array('apply' => true);
+		$this->ui->edit($content);
+
+		$info = $this->bo->read($this->info_id);
+
+		// Check original pm_id is there
+		$this->assertNotNull($info['pm_id'], 'Original project was not set');
+		$this->assertEquals($pm_id_1, $info['pm_id'], 'Original project went missing');
+
+		$this->check_links($this->info_id, $pm_id_1, $pm_id_2);
+	}
+
+	protected function check_links($info_id, $pm_id_1, $pm_id_2)
+	{
+		// Check links
+		$links = Link::get_links('infolog', $info_id);
+		$all_there = count(array_filter($links, function ($link) use ($pm_id_1)
+						   {
+							   return $link['id'] == $pm_id_1;
+						   })
+			) == 1;
+		$this->assertTrue($all_there, "First project went missing");
+		$all_there = count(array_filter($links, function ($link) use ($pm_id_2)
+						   {
+							   return $link['id'] == $pm_id_2;
+						   })
+			) == 1;
+		$this->assertTrue($all_there, "Second project went missing");
+	}
+
+	protected function getTestInfolog()
+	{
+		return array(
+			'info_subject' => 'Test Infolog Entry for ' . $this->getName()
+		);
+	}
+
+	/**
+	 * Make a project so we can test deleting it
+	 */
+	protected function makeProject($pm_number = '')
+	{
+		$project = array(
+			'pm_number'      => 'TEST' . ($pm_number ? " $pm_number" : ''),
+			'pm_title'       => 'Auto-test for ' . $this->getName(),
+			'pm_status'      => 'active',
+			'pm_description' => 'Test project for ' . $this->getName()
+		);
+
+		// Save & set modifier, no notifications
+		try
+		{
+			$result = true;
+			$result = $this->pm_bo->save($project, true, false);
+		}
+		catch (\Exception $e)
+		{
+			// Something went wrong, we'll just fail
+			$this->fail($e);
+		}
+
+		$this->assertFalse((boolean)$result, 'Error making test project');
+		$this->assertArrayHasKey('pm_id', $this->pm_bo->data, 'Could not make test project');
+		$this->assertThat($this->pm_bo->data['pm_id'],
+						  $this->logicalAnd(
+							  $this->isType('integer'),
+							  $this->greaterThan(0)
+						  )
+		);
+		$this->pm_id[] = $this->pm_bo->data['pm_id'];
+	}
+
+	/**
+	 * Check that the project element is present
+	 *
+	 */
+	protected function checkElements($pm_id, $expected_count = 1)
+	{
+		$element_bo = new \projectmanager_elements_bo();
+		$element_count = 0;
+
+		foreach((array)$element_bo->search(array('pm_id' => $pm_id), false) as $element)
+		{
+			$element_count++;
+			$this->assertEquals($this->info_id, $element['pe_app_id']);
+		}
+
+		$this->assertEquals($expected_count, $element_count, "Incorrect number of elements");
+	}
+
+	/**
+	 * Fully delete a project and its elements, no matter what state or settings
+	 */
+	protected function deleteProject()
+	{
+		// Force links to run notification now, or elements might stay
+		// usually waits until Egw::on_shutdown();
+		Link::run_notifies();
+
+		// Force to ignore setting
+		$this->pm_bo->history = '';
+		foreach($this->pm_id as $pm_id)
+		{
+			$this->pm_bo->delete($pm_id, true);
+		}
+
+		// Force links to run notification now, or elements might stay
+		// usually waits until Egw::on_shutdown();
+		Link::run_notifies();
+	}
+
+}
\ No newline at end of file