Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
14.04% covered (danger)
14.04%
223 / 1588
10.00% covered (danger)
10.00%
12 / 120
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageController
14.04% covered (danger)
14.04%
223 / 1588
10.00% covered (danger)
10.00%
12 / 120
171591.88
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 index
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 getSvgFromApp
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getSvg
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 colorizeSvg
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 indexProject
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 indexBill
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 pubLoginProjectPassword
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
2
 pubLoginProject
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 pubLogin
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
 publicShareLinkPage
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
20
 pubProject
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 1
110
 checkLogin
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
56
 webCreateProject
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 webDeleteProject
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 webDeleteBill
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 webDeleteBills
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 webGetProjectInfo
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 webGetProjectStatistics
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 webGetProjectSettlement
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 webAutoSettlement
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 webCheckPassword
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 webEditMember
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
6.09
 webEditBill
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
3
 webMoveBill
90.00% covered (success)
90.00%
18 / 20
0.00% covered (danger)
0.00%
0 / 1
4.02
 webRepeatBill
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 webEditBills
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
 webEditProject
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 webAddBill
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 webAddMember
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 webGetBills
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
3
 webGetProjects
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 webGetProjects2
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 apiCreateProject
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 apiPrivCreateProject
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 apiGetProjectInfo
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
 apiPrivGetProjectInfo
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 apiSetProjectInfo
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivSetProjectInfo
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 apiGetMembers
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 apiPrivGetMembers
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 apiGetBills
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 apiv3GetBills
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 apiPrivGetBills
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 apiv2GetBills
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 apiAddMember
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiv2AddMember
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivAddMember
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiAddBill
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
110
 apiPrivAddBill
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 apiRepeatBill
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
56
 apiEditBill
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
110
 apiEditBills
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
132
 apiPrivEditBill
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 apiDeleteBill
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
156
 apiDeleteBills
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
182
 apiPrivDeleteBill
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
30
 apiDeleteMember
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivDeleteMember
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiDeleteProject
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivDeleteProject
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiEditMember
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
132
 apiPrivEditMember
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 apiGetProjectStatistics
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 apiPrivGetProjectStatistics
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 apiGetProjectSettlement
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 apiPrivGetProjectSettlement
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 apiAutoSettlement
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivAutoSettlement
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 editShareAccessLevel
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 editShareAccess
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 editGuestAccessLevel
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 apiEditGuestAccessLevel
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 addPaymentMode
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
4.54
 apiAddPaymentMode
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivAddPaymentMode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 editPaymentMode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 savePaymentModeOrder
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 apiEditPaymentMode
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiSavePaymentModeOrder
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivEditPaymentMode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 deletePaymentMode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiDeletePaymentMode
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivDeletePaymentMode
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 addCategory
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
4.54
 apiAddCategory
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivAddCategory
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 editCategory
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 saveCategoryOrder
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 apiEditCategory
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiSaveCategoryOrder
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivEditCategory
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 deleteCategory
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiDeleteCategory
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivDeleteCategory
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 addCurrency
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiAddCurrency
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivAddCurrency
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 editCurrency
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiEditCurrency
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivEditCurrency
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 deleteCurrency
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 apiDeleteCurrency
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 apiPrivDeleteCurrency
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 addUserShare
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
3.79
 deleteUserShare
54.55% covered (warning)
54.55%
6 / 11
0.00% covered (danger)
0.00%
0 / 1
5.50
 addPublicShare
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 deletePublicShare
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 addGroupShare
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
3.79
 deleteGroupShare
63.64% covered (warning)
63.64%
7 / 11
0.00% covered (danger)
0.00%
0 / 1
4.77
 addCircleShare
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 deleteCircleShare
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getPublicFileShare
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
56
 exportCsvSettlement
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 exportCsvStatistics
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 exportCsvProject
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 importCsvProject
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 importSWProject
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 apiPing
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getBillActivity
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Nextcloud - cospend
4 *
5 * This file is licensed under the Affero General Public License version 3 or
6 * later. See the COPYING file.
7 *
8 * @author Julien Veyssier <eneiluj@posteo.net>
9 * @copyright Julien Veyssier 2019
10 */
11
12namespace OCA\Cospend\Controller;
13
14use DateTime;
15use OC\Files\Filesystem;
16use OCP\App\AppPathNotFoundException;
17use OCP\App\IAppManager;
18use OCP\AppFramework\Http;
19use OCP\AppFramework\Http\DataDisplayResponse;
20use OCP\AppFramework\Http\NotFoundResponse;
21use OCP\AppFramework\Http\Response;
22use OCP\AppFramework\Services\IInitialState;
23use OCP\IConfig;
24use OCP\IL10N;
25
26use OCP\AppFramework\Http\ContentSecurityPolicy;
27
28use OCP\IRequest;
29use OCP\AppFramework\Http\TemplateResponse;
30use OCP\AppFramework\Http\Template\PublicTemplateResponse;
31use OCP\AppFramework\Http\DataResponse;
32use OCP\AppFramework\ApiController;
33use OCP\Constants;
34use OCP\Files\FileInfo;
35use OCP\Share\IShare;
36use OCP\DB\QueryBuilder\IQueryBuilder;
37use OCP\IUserManager;
38use OCP\Share\IManager;
39use OCP\Files\IRootFolder;
40use OCP\IDBConnection;
41
42use OCA\Cospend\Db\BillMapper;
43use OCA\Cospend\Service\ProjectService;
44use OCA\Cospend\Activity\ActivityManager;
45use OCA\Cospend\AppInfo\Application;
46
47class PageController extends ApiController {
48
49    /**
50     * @var IConfig
51     */
52    private $config;
53    /**
54     * @var IManager
55     */
56    private $shareManager;
57    /**
58     * @var IUserManager
59     */
60    private $userManager;
61    /**
62     * @var IL10N
63     */
64    private $trans;
65    /**
66     * @var BillMapper
67     */
68    private $billMapper;
69    /**
70     * @var ProjectService
71     */
72    private $projectService;
73    /**
74     * @var ActivityManager
75     */
76    private $activityManager;
77    /**
78     * @var IDBConnection
79     */
80    private $dbconnection;
81    /**
82     * @var IRootFolder
83     */
84    private $root;
85    /**
86     * @var string|null
87     */
88    private $userId;
89    /**
90     * @var IInitialState
91     */
92    private $initialStateService;
93    /**
94     * @var IAppManager
95     */
96    private $appManager;
97
98    public function __construct(string $appName,
99                                IRequest $request,
100                                IConfig $config,
101                                IManager $shareManager,
102                                IUserManager $userManager,
103                                IL10N $trans,
104                                BillMapper $billMapper,
105                                ProjectService $projectService,
106                                ActivityManager $activityManager,
107                                IDBConnection $dbconnection,
108                                IRootFolder $root,
109                                IInitialState $initialStateService,
110                                IAppManager $appManager,
111                                ?string $userId){
112        parent::__construct($appName, $request,
113                            'PUT, POST, GET, DELETE, PATCH, OPTIONS',
114                            'Authorization, Content-Type, Accept',
115                            1728000);
116        $this->config = $config;
117        $this->shareManager = $shareManager;
118        $this->userManager = $userManager;
119        $this->trans = $trans;
120        $this->billMapper = $billMapper;
121        $this->projectService = $projectService;
122        $this->activityManager = $activityManager;
123        $this->dbconnection = $dbconnection;
124        $this->root = $root;
125        $this->userId = $userId;
126        $this->initialStateService = $initialStateService;
127        $this->appManager = $appManager;
128    }
129
130    /**
131     * Main page
132     * @NoAdminRequired
133     * @NoCSRFRequired
134     */
135    public function index(?string $projectId = null, ?int $billId = null): TemplateResponse {
136        $activityEnabled = $this->appManager->isEnabledForUser('activity');
137        $this->initialStateService->provideInitialState('activity_enabled', $activityEnabled ? '1' : '0');
138        $this->initialStateService->provideInitialState('pathProjectId', $projectId ?? '');
139        $this->initialStateService->provideInitialState('pathBillId', $billId ?? 0);
140        $response = new TemplateResponse('cospend', 'main', []);
141        $csp = new ContentSecurityPolicy();
142        $csp->addAllowedImageDomain('*')
143            ->addAllowedMediaDomain('*')
144//            ->addAllowedChildSrcDomain('*')
145            ->addAllowedFrameDomain('*')
146            ->addAllowedWorkerSrcDomain('*')
147            //->allowInlineScript(true)
148            // to make eval work in frontend
149            ->allowEvalScript(true)
150            ->addAllowedObjectDomain('*')
151            ->addAllowedScriptDomain('*')
152            ->addAllowedConnectDomain('*');
153        $response->setContentSecurityPolicy($csp);
154        return $response;
155    }
156
157    /**
158     * @NoAdminRequired
159     * @NoCSRFRequired
160     * @param string $fileName
161     * @param string $color
162     * @return NotFoundResponse|Response
163     */
164    public function getSvgFromApp(string $fileName, string $color = 'ffffff') {
165        try {
166            $appPath = $this->appManager->getAppPath(Application::APP_ID);
167        } catch (AppPathNotFoundException $e) {
168            return new NotFoundResponse();
169        }
170
171        $path = $appPath . "/img/$fileName.svg";
172        return $this->getSvg($path, $color, $fileName);
173    }
174
175    private function getSvg(string $path, string $color, string $fileName): Response {
176        if (!Filesystem::isValidPath($path)) {
177            return new NotFoundResponse();
178        }
179
180        if (!file_exists($path)) {
181            return new NotFoundResponse();
182        }
183
184        $svg = file_get_contents($path);
185
186        if ($svg === null) {
187            return new NotFoundResponse();
188        }
189
190        $svg = $this->colorizeSvg($svg, $color);
191
192        $response = new DataDisplayResponse($svg, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']);
193
194        // Set cache control
195        $ttl = 31536000;
196        $response->cacheFor($ttl);
197
198        return $response;
199    }
200
201    public function colorizeSvg(string $svg, string $color): string {
202        if (!preg_match('/^[0-9a-f]{3,6}$/i', $color)) {
203            // Prevent not-sane colors from being written into the SVG
204            $color = '000';
205        }
206
207        // add fill (fill is not present on black elements)
208        $fillRe = '/<((circle|rect|path)((?!fill)[a-z0-9 =".\-#():;,])+)\/>/mi';
209        $svg = preg_replace($fillRe, '<$1 fill="#' . $color . '"/>', $svg);
210
211        // replace any fill or stroke colors
212        $svg = preg_replace('/stroke="#([a-z0-9]{3,6})"/mi', 'stroke="#' . $color . '"', $svg);
213        $svg = preg_replace('/fill="#([a-z0-9]{3,6})"/mi', 'fill="#' . $color . '"', $svg);
214        return $svg;
215    }
216
217    /**
218     * Main page
219     * @NoAdminRequired
220     * @NoCSRFRequired
221     */
222    public function indexProject(string $projectId): TemplateResponse {
223        return $this->index($projectId);
224    }
225
226    /**
227     * Main page
228     * @NoAdminRequired
229     * @NoCSRFRequired
230     */
231    public function indexBill(string $projectId, int $billId): TemplateResponse {
232        return $this->index($projectId, $billId);
233    }
234
235    /**
236     * @NoAdminRequired
237     * @NoCSRFRequired
238     * @PublicPage
239     */
240    public function pubLoginProjectPassword(string $projectid, string $password = ''): PublicTemplateResponse {
241        // PARAMS to view
242        $params = [
243            'projectid' => $projectid,
244            'password' => $password,
245            'wrong' => false,
246        ];
247        $response = new PublicTemplateResponse('cospend', 'login', $params);
248        $response->setHeaderTitle($this->trans->t('Cospend public access'));
249        $response->setHeaderDetails($this->trans->t('Enter password of project %s', [$projectid]));
250        $response->setFooterVisible(false);
251        $csp = new ContentSecurityPolicy();
252        $csp->addAllowedImageDomain('*')
253            ->addAllowedMediaDomain('*')
254            //->addAllowedChildSrcDomain('*')
255            ->addAllowedFrameDomain('*')
256            ->addAllowedWorkerSrcDomain('*')
257            ->addAllowedObjectDomain('*')
258            ->addAllowedScriptDomain('*')
259            ->addAllowedConnectDomain('*');
260        $response->setContentSecurityPolicy($csp);
261        return $response;
262    }
263
264    /**
265     * @NoAdminRequired
266     * @NoCSRFRequired
267     * @PublicPage
268     */
269    public function pubLoginProject(string $projectid): PublicTemplateResponse {
270        // PARAMS to view
271        $params = [
272            'projectid' => $projectid,
273            'wrong' => false,
274        ];
275        $response = new PublicTemplateResponse('cospend', 'login', $params);
276        $response->setHeaderTitle($this->trans->t('Cospend public access'));
277        $response->setHeaderDetails($this->trans->t('Enter password of project %s', [$projectid]));
278        $response->setFooterVisible(false);
279        $csp = new ContentSecurityPolicy();
280        $csp->addAllowedImageDomain('*')
281            ->addAllowedMediaDomain('*')
282            //->addAllowedChildSrcDomain('*')
283            ->addAllowedFrameDomain('*')
284            ->addAllowedWorkerSrcDomain('*')
285            ->addAllowedObjectDomain('*')
286            ->addAllowedScriptDomain('*')
287            ->addAllowedConnectDomain('*');
288        $response->setContentSecurityPolicy($csp);
289        return $response;
290    }
291
292    /**
293     * @NoAdminRequired
294     * @NoCSRFRequired
295     * @PublicPage
296     */
297    public function pubLogin(): PublicTemplateResponse {
298        // PARAMS to view
299        $params = [
300            'wrong' => false,
301        ];
302        $response = new PublicTemplateResponse('cospend', 'login', $params);
303        $response->setHeaderTitle($this->trans->t('Cospend public access'));
304        $response->setHeaderDetails($this->trans->t('Enter project id and password'));
305        $response->setFooterVisible(false);
306        $csp = new ContentSecurityPolicy();
307        $csp->addAllowedImageDomain('*')
308            ->addAllowedMediaDomain('*')
309            //->addAllowedChildSrcDomain('*')
310            ->addAllowedFrameDomain('*')
311            ->addAllowedWorkerSrcDomain('*')
312            ->addAllowedObjectDomain('*')
313            ->addAllowedScriptDomain('*')
314            ->addAllowedConnectDomain('*');
315        $response->setContentSecurityPolicy($csp);
316        return $response;
317    }
318
319    /**
320     * @NoAdminRequired
321     * @NoCSRFRequired
322     * @PublicPage
323     */
324    public function publicShareLinkPage(string $token): PublicTemplateResponse {
325        $isMain = false;
326        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($token);
327        if (!is_null($publicShareInfo)) {
328            $isPasswordProtected = !is_null($publicShareInfo['password'] ?? null);
329            if ($isPasswordProtected) {
330                $params = [
331                    'projecttoken' => $token,
332                    'wrong' => false,
333                ];
334                $response = new PublicTemplateResponse('cospend', 'sharepassword', $params);
335                $response->setHeaderDetails($this->trans->t('Enter link password of project %s', [$publicShareInfo['projectid']]));
336            } else {
337                $this->initialStateService->provideInitialState('projectid', $token);
338                $this->initialStateService->provideInitialState('password', 'nopass');
339
340                $response = new PublicTemplateResponse('cospend', 'main', []);
341                $response->setHeaderDetails($this->trans->t('Project %s', [$publicShareInfo['projectid']]));
342                $isMain = true;
343            }
344            $response->setHeaderTitle($this->trans->t('Cospend shared link access'));
345            $response->setFooterVisible(false);
346        } else {
347            $response = new PublicTemplateResponse('cospend', 'error', []);
348            $response->setHeaderTitle($this->trans->t('No such share link'));
349            $response->setHeaderDetails($this->trans->t('Access denied'));
350        }
351        $response->setFooterVisible(false);
352        $csp = new ContentSecurityPolicy();
353        $csp->addAllowedImageDomain('*')
354            ->addAllowedMediaDomain('*')
355            //->addAllowedChildSrcDomain('*')
356            ->addAllowedFrameDomain('*')
357            ->addAllowedWorkerSrcDomain('*')
358            ->addAllowedObjectDomain('*')
359            ->addAllowedScriptDomain('*')
360            ->addAllowedConnectDomain('*');
361        if ($isMain) {
362            $csp->allowEvalScript(true);
363        }
364        $response->setContentSecurityPolicy($csp);
365        return $response;
366    }
367
368    /**
369     * @NoAdminRequired
370     * @NoCSRFRequired
371     * @PublicPage
372     */
373    public function pubProject(?string $projectid = null, ?string $password = null, ?string $projecttoken = null): PublicTemplateResponse {
374        if (!is_null($projectid) && !is_null($password)) {
375            if ($this->checkLogin($projectid, $password)) {
376                $this->initialStateService->provideInitialState('projectid', $projectid);
377                $this->initialStateService->provideInitialState('password', $password);
378                $response = new PublicTemplateResponse('cospend', 'main', []);
379                $response->setHeaderTitle($this->trans->t('Cospend public access'));
380                $response->setHeaderDetails($this->trans->t('Project %s', [$projectid]));
381                $response->setFooterVisible(false);
382                $csp = new ContentSecurityPolicy();
383                $csp->addAllowedImageDomain('*')
384                    ->addAllowedMediaDomain('*')
385                    //->addAllowedChildSrcDomain('*')
386                    ->addAllowedFrameDomain('*')
387                    ->addAllowedWorkerSrcDomain('*')
388                    ->allowEvalScript(true)
389                    ->addAllowedObjectDomain('*')
390                    ->addAllowedScriptDomain('*')
391                    ->addAllowedConnectDomain('*');
392                $response->setContentSecurityPolicy($csp);
393                return $response;
394            } else {
395                //$response = new DataResponse(null, 403);
396                //return $response;
397                $params = [
398                    'wrong' => true,
399                ];
400                $response = new PublicTemplateResponse('cospend', 'login', $params);
401                $response->setHeaderTitle($this->trans->t('Cospend public access'));
402                $response->setHeaderDetails($this->trans->t('Access denied'));
403                $response->setFooterVisible(false);
404                $csp = new ContentSecurityPolicy();
405                $csp->addAllowedImageDomain('*')
406                    ->addAllowedMediaDomain('*')
407                    //->addAllowedChildSrcDomain('*')
408                    ->addAllowedFrameDomain('*')
409                    ->addAllowedWorkerSrcDomain('*')
410                    ->addAllowedObjectDomain('*')
411                    ->addAllowedScriptDomain('*')
412                    ->addAllowedConnectDomain('*');
413                $response->setContentSecurityPolicy($csp);
414                return $response;
415            }
416        } elseif (!is_null($projecttoken) && !is_null($password)) {
417            $info = $this->projectService->getProjectInfoFromShareToken($projecttoken);
418            // if the token is good and no password (or it matches the share one)
419            if (!is_null($info['projectid'] ?? null)
420                && (is_null($info['password'] ?? null) || $password === $info['password'])
421            ) {
422                $this->initialStateService->provideInitialState('projectid', $projecttoken);
423                $this->initialStateService->provideInitialState('password', $password);
424
425                $response = new PublicTemplateResponse('cospend', 'main', []);
426                $response->setHeaderTitle($this->trans->t('Cospend shared link access'));
427                $response->setHeaderDetails($this->trans->t('Project %s', [$info['projectid']]));
428                $response->setFooterVisible(false);
429                $csp = new ContentSecurityPolicy();
430                $csp->addAllowedImageDomain('*')
431                    ->addAllowedMediaDomain('*')
432                    //->addAllowedChildSrcDomain('*')
433                    ->addAllowedFrameDomain('*')
434                    ->addAllowedWorkerSrcDomain('*')
435                    ->allowEvalScript(true)
436                    ->addAllowedObjectDomain('*')
437                    ->addAllowedScriptDomain('*')
438                    ->addAllowedConnectDomain('*');
439                $response->setContentSecurityPolicy($csp);
440                return $response;
441            } elseif (!is_null($info['projectid'] ?? null)) {
442                $params = [
443                    'projecttoken' => $projecttoken,
444                    'wrong' => true,
445                ];
446                $response = new PublicTemplateResponse('cospend', 'sharepassword', $params);
447                $response->setHeaderTitle($this->trans->t('Cospend shared link access'));
448                $response->setHeaderDetails($this->trans->t('Enter link password of project %s', [$info['projectid']]));
449                $response->setFooterVisible(false);
450                $csp = new ContentSecurityPolicy();
451                $csp->addAllowedImageDomain('*')
452                    ->addAllowedMediaDomain('*')
453                    //->addAllowedChildSrcDomain('*')
454                    ->addAllowedFrameDomain('*')
455                    ->addAllowedWorkerSrcDomain('*')
456                    ->addAllowedObjectDomain('*')
457                    ->addAllowedScriptDomain('*')
458                    ->addAllowedConnectDomain('*');
459                $response->setContentSecurityPolicy($csp);
460                return $response;
461            }
462        }
463        // TODO return error page
464        $response = new PublicTemplateResponse('cospend', 'error', []);
465        $response->setHeaderTitle($this->trans->t('No such share link or public access'));
466        $response->setHeaderDetails($this->trans->t('Access denied'));
467        return $response;
468    }
469
470    /**
471     * Check if project password is valid
472     *
473     * @param string $projectId
474     * @param string $password
475     * @return bool
476     */
477    private function checkLogin(string $projectId, string $password): bool {
478        if ($projectId === '' || $projectId === null
479            || $password === '' || $password === null
480        ) {
481            return false;
482        } else {
483            $qb = $this->dbconnection->getQueryBuilder();
484            $qb->select('id', 'password')
485               ->from('cospend_projects', 'p')
486               ->where(
487                   $qb->expr()->eq('id', $qb->createNamedParameter($projectId, IQueryBuilder::PARAM_STR))
488               );
489            $req = $qb->executeQuery();
490            $dbPassword = null;
491            $row = $req->fetch();
492            if ($row !== false) {
493                $dbPassword = $row['password'];
494            }
495            $req->closeCursor();
496            $qb->resetQueryParts();
497            return (
498                $dbPassword !== null &&
499                password_verify($password, $dbPassword)
500            );
501        }
502    }
503
504    /**
505     * @NoAdminRequired
506     *
507     */
508    public function webCreateProject(string $id, string $name, ?string $password = null): DataResponse {
509        $user = $this->userManager->get($this->userId);
510        $userEmail = $user->getEMailAddress();
511        $result = $this->projectService->createProject($name, $id, $password, $userEmail, $this->userId);
512        if (isset($result['id'])) {
513            $projInfo = $this->projectService->getProjectInfo($result['id']);
514            $projInfo['myaccesslevel'] = Application::ACCESS_LEVELS['admin'];
515            return new DataResponse($projInfo);
516        } else {
517            return new DataResponse($result, 400);
518        }
519    }
520
521    /**
522     * @NoAdminRequired
523     *
524     */
525    public function webDeleteProject(string $projectid): DataResponse {
526        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['admin']) {
527            $result = $this->projectService->deleteProject($projectid);
528            if (!isset($result['error'])) {
529                return new DataResponse($result);
530            } else {
531                return new DataResponse(['message' => $result['error']], 404);
532            }
533        } else {
534            return new DataResponse(
535                ['message' => $this->trans->t('Unauthorized action')],
536                403
537            );
538        }
539    }
540
541    /**
542     * @NoAdminRequired
543     *
544     */
545    public function webDeleteBill(string $projectid, int $billid): DataResponse {
546        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
547            $billObj = null;
548            if ($this->projectService->getBill($projectid, $billid) !== null) {
549                $billObj = $this->billMapper->find($billid);
550            }
551
552            $result = $this->projectService->deleteBill($projectid, $billid);
553            if (isset($result['success'])) {
554                if (!is_null($billObj)) {
555                    $this->activityManager->triggerEvent(
556                        ActivityManager::COSPEND_OBJECT_BILL, $billObj,
557                        ActivityManager::SUBJECT_BILL_DELETE,
558                        []
559                    );
560                }
561                return new DataResponse('OK');
562            } else {
563                return new DataResponse($result, 404);
564            }
565        } else {
566            return new DataResponse(
567                ['message' => $this->trans->t('You are not allowed to delete this bill')],
568                403
569            );
570        }
571    }
572
573    /**
574     * @NoAdminRequired
575     *
576     */
577    public function webDeleteBills(string $projectid, array $billIds): DataResponse {
578        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
579            foreach ($billIds as $billid) {
580                $billObj = null;
581                if ($this->projectService->getBill($projectid, $billid) !== null) {
582                    $billObj = $this->billMapper->find($billid);
583                }
584                $result = $this->projectService->deleteBill($projectid, $billid);
585                if (!isset($result['success'])) {
586                    return new DataResponse($result, 400);
587                } else {
588                    if (!is_null($billObj)) {
589                        $this->activityManager->triggerEvent(
590                            ActivityManager::COSPEND_OBJECT_BILL, $billObj,
591                            ActivityManager::SUBJECT_BILL_DELETE,
592                            []
593                        );
594                    }
595                }
596            }
597            return new DataResponse('OK');
598        } else {
599            return new DataResponse(
600                ['message' => $this->trans->t('You are not allowed to delete this bill')],
601                403
602            );
603        }
604    }
605
606    /**
607     * @NoAdminRequired
608     *
609     */
610    public function webGetProjectInfo(string $projectid): DataResponse {
611        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
612            $projectInfo = $this->projectService->getProjectInfo($projectid);
613            $projectInfo['myaccesslevel'] = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
614            return new DataResponse($projectInfo);
615        } else {
616            return new DataResponse(
617                ['message' => $this->trans->t('You are not allowed to get this project\'s info')],
618                403
619            );
620        }
621    }
622
623    /**
624     * @NoAdminRequired
625     *
626     */
627    public function webGetProjectStatistics(string $projectid, ?int $tsMin = null, ?int $tsMax = null, ?int $paymentModeId = null,
628                                            ?int   $categoryId = null, ?float $amountMin = null, ?float $amountMax = null,
629                                            string $showDisabled = '1', ?int $currencyId = null,
630                                            ?int $payerId = null): DataResponse {
631        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
632            $result = $this->projectService->getProjectStatistics(
633                $projectid, 'lowername', $tsMin, $tsMax, $paymentModeId,
634                $categoryId, $amountMin, $amountMax, $showDisabled === '1', $currencyId, $payerId
635            );
636            return new DataResponse($result);
637        } else {
638            return new DataResponse(
639                ['message' => $this->trans->t('You are not allowed to get this project\'s statistics')],
640                403
641            );
642        }
643    }
644
645    /**
646     * @NoAdminRequired
647     *
648     */
649    public function webGetProjectSettlement(string $projectid, ?int $centeredOn = null, ?int $maxTimestamp = null): DataResponse {
650        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
651            $result = $this->projectService->getProjectSettlement($projectid, $centeredOn, $maxTimestamp);
652            return new DataResponse($result);
653        }
654        else {
655            return new DataResponse(
656                ['message' => $this->trans->t('You are not allowed to get this project\'s settlement')],
657                403
658            );
659        }
660    }
661
662    /**
663     * @NoAdminRequired
664     *
665     */
666    public function webAutoSettlement(string $projectid, ?int $centeredOn = null, int $precision = 2, ?int $maxTimestamp = null): DataResponse {
667        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
668            $result = $this->projectService->autoSettlement($projectid, $centeredOn, $precision, $maxTimestamp);
669            if (isset($result['success'])) {
670                return new DataResponse('OK');
671            } else {
672                return new DataResponse($result, 403);
673            }
674        } else {
675            return new DataResponse(
676                ['message' => $this->trans->t('You are not allowed to settle this project automatically')],
677                403
678            );
679        }
680    }
681
682    /**
683     * @NoAdminRequired
684     *
685     */
686    public function webCheckPassword(string $projectid, string $password): DataResponse {
687        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
688            return new DataResponse($this->checkLogin($projectid, $password));
689        } else {
690            return new DataResponse(
691                ['message' => $this->trans->t('You are not allowed to access this project')],
692                403
693            );
694        }
695    }
696
697    /**
698     * @NoAdminRequired
699     *
700     */
701    public function webEditMember(string $projectid, int $memberid, ?string $name = null,
702                                ?float $weight = null, $activated = null, ?string $color = null,
703                                ?string $userid = null): DataResponse {
704        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
705            if ($activated === 'true') {
706                $activated = true;
707            } elseif ($activated === 'false') {
708                $activated = false;
709            }
710            $result = $this->projectService->editMember($projectid, $memberid, $name, $userid, $weight, $activated, $color);
711            if (count($result) === 0) {
712                return new DataResponse(null);
713            } elseif (array_key_exists('activated', $result)) {
714                return new DataResponse($result);
715            } else {
716                return new DataResponse($result, 400);
717            }
718        } else {
719            return new DataResponse(
720                ['message' => $this->trans->t('You are not allowed to edit this member')],
721                403
722            );
723        }
724    }
725
726    /**
727     * @NoAdminRequired
728     *
729     */
730    public function webEditBill(string $projectid, int $billid, ?string $date = null, ?string $what = null, ?int $payer = null,
731                                ?string $payed_for = null, ?float $amount = null, ?string $repeat = null,
732                                ?string $paymentmode = null, ?int $paymentmodeid = null,
733                                ?int $categoryid = null, ?int $repeatallactive = null, ?string $repeatuntil = null,
734                                ?int $timestamp = null, ?string $comment = null, ?int $repeatfreq = null): DataResponse {
735        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
736        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant']) {
737            $result =  $this->projectService->editBill($projectid, $billid, $date, $what, $payer, $payed_for,
738                                                       $amount, $repeat, $paymentmode, $paymentmodeid, $categoryid,
739                                                       $repeatallactive, $repeatuntil, $timestamp, $comment, $repeatfreq);
740            if (isset($result['edited_bill_id'])) {
741                $billObj = $this->billMapper->find($billid);
742                $this->activityManager->triggerEvent(
743                    ActivityManager::COSPEND_OBJECT_BILL, $billObj,
744                    ActivityManager::SUBJECT_BILL_UPDATE,
745                    []
746                );
747
748                return new DataResponse($result['edited_bill_id']);
749            } else {
750                return new DataResponse($result, 400);
751            }
752        } else {
753            return new DataResponse(
754                ['message' => $this->trans->t('You are not allowed to edit this bill')],
755                403
756            );
757        }
758    }
759
760    /**
761     * @NoAdminRequired
762     */
763    public function webMoveBill(string $projectid, int $billid, string $toProjectId): DataResponse {
764        // ensure the user has permission to access both projects
765        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
766
767        if ($userAccessLevel < Application::ACCESS_LEVELS['participant']) {
768            return new DataResponse(['message' => $this->trans->t('You are not allowed to edit this bill')], 403);
769        }
770
771        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $toProjectId);
772
773        if ($userAccessLevel < Application::ACCESS_LEVELS['participant']) {
774            return new DataResponse(['message' => $this->trans->t ('You are not allowed to access the destination project')], 403);
775        }
776
777        // get current bill from mapper for the activity manager
778        $oldBillObj = $this->billMapper->find ($billid);
779
780        // update the bill information
781        $result = $this->projectService->moveBill($projectid, $billid, $toProjectId);
782
783        if (!isset($result['inserted_id'])) {
784            return new DataResponse($result, 403);
785        }
786
787        $newBillObj = $this->billMapper->find ($result ['inserted_id']);
788
789        // add delete activity record
790        $this->activityManager->triggerEvent (
791            ActivityManager::COSPEND_OBJECT_BILL, $oldBillObj,
792            ActivityManager::SUBJECT_BILL_DELETE, []
793        );
794
795        // add create activity record
796        $this->activityManager->triggerEvent (
797            ActivityManager::COSPEND_OBJECT_BILL, $newBillObj,
798            ActivityManager::SUBJECT_BILL_CREATE, []
799        );
800
801        // return a 200 response
802        return new DataResponse($result['inserted_id']);
803    }
804
805    /**
806     * @NoAdminRequired
807     *
808     */
809    public function webRepeatBill(string $projectid, int $billid): DataResponse {
810        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
811        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant']) {
812            $result = $this->projectService->cronRepeatBills($billid);
813            return new DataResponse($result);
814        } else {
815            return new DataResponse(
816                ['message' => $this->trans->t('You are not allowed to add bills')],
817                403
818            );
819        }
820    }
821
822    /**
823     * @NoAdminRequired
824     *
825     */
826    public function webEditBills(string $projectid, array $billIds, ?int $categoryid = null, ?string $date = null,
827                                ?string $what = null, ?int $payer = null, ?string $payed_for = null,
828                                ?float $amount = null, ?string $repeat = null,
829                                 ?string $paymentmode = null, ?int $paymentmodeid = null,
830                                ?int $repeatallactive = null, ?string $repeatuntil = null, ?int $timestamp = null,
831                                ?string $comment = null, ?int $repeatfreq = null): DataResponse {
832        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
833        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant']) {
834            $paymentModes = $this->projectService->getCategoriesOrPaymentModes($projectid, false);
835            foreach ($billIds as $billid) {
836                $result =  $this->projectService->editBill(
837                    $projectid, $billid, $date, $what, $payer, $payed_for,
838                    $amount, $repeat, $paymentmode, $paymentmodeid, $categoryid,
839                    $repeatallactive, $repeatuntil, $timestamp, $comment,
840                    $repeatfreq, $paymentModes
841                );
842                if (isset($result['edited_bill_id'])) {
843                    $billObj = $this->billMapper->find($billid);
844                    $this->activityManager->triggerEvent(
845                        ActivityManager::COSPEND_OBJECT_BILL, $billObj,
846                        ActivityManager::SUBJECT_BILL_UPDATE,
847                        []
848                    );
849                } else {
850                    return new DataResponse($result, 400);
851                }
852            }
853            return new DataResponse($billIds);
854        } else {
855            return new DataResponse(
856                ['message' => $this->trans->t('You are not allowed to edit this bill')],
857                403
858            );
859        }
860    }
861
862    /**
863     * @NoAdminRequired
864     *
865     */
866    public function webEditProject(string $projectid, ?string $name = null, ?string $contact_email = null, ?string $password = null,
867                                    ?string $autoexport = null, ?string $currencyname = null, ?bool $deletion_disabled = null,
868                                    ?string $categorysort = null, ?string $paymentmodesort = null): DataResponse {
869        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['admin']) {
870            $result = $this->projectService->editProject(
871                $projectid, $name, $contact_email, $password, $autoexport,
872                $currencyname, $deletion_disabled, $categorysort, $paymentmodesort
873            );
874            if (isset($result['success'])) {
875                return new DataResponse('UPDATED');
876            } else {
877                return new DataResponse($result, 400);
878            }
879        } else {
880            return new DataResponse(
881                ['message' => $this->trans->t('You are not allowed to edit this project')],
882                403
883            );
884        }
885    }
886
887    /**
888     * @NoAdminRequired
889     *
890     */
891    public function webAddBill(string $projectid, ?string $date = null, ?string $what = null, ?int $payer = null, ?string $payed_for = null,
892                            ?float $amount = null, ?string $repeat = null, ?string $paymentmode = null, ?int $paymentmodeid = null,
893                            ?int $categoryid = null, int $repeatallactive = 0, ?string $repeatuntil = null, ?int $timestamp = null,
894                            ?string $comment = null, ?int $repeatfreq = null): DataResponse {
895        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
896            $result = $this->projectService->addBill($projectid, $date, $what, $payer, $payed_for, $amount,
897                                                     $repeat, $paymentmode, $paymentmodeid, $categoryid, $repeatallactive,
898                                                     $repeatuntil, $timestamp, $comment, $repeatfreq);
899            if (isset($result['inserted_id'])) {
900                $billObj = $this->billMapper->find($result['inserted_id']);
901                $this->activityManager->triggerEvent(
902                    ActivityManager::COSPEND_OBJECT_BILL, $billObj,
903                    ActivityManager::SUBJECT_BILL_CREATE,
904                    []
905                );
906                return new DataResponse($result['inserted_id']);
907            } else {
908                return new DataResponse($result, 400);
909            }
910        } else {
911            return new DataResponse(
912                ['message' => $this->trans->t('You are not allowed to add bills')],
913                403
914            );
915        }
916    }
917
918    /**
919     * @NoAdminRequired
920     *
921     */
922    public function webAddMember(string $projectid, string $name, ?string $userid = null,
923                                float $weight = 1, int $active = 1, ?string $color=null): DataResponse {
924        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
925            $result = $this->projectService->addMember($projectid, $name, $weight, $active !== 0, $color, $userid);
926            if (!isset($result['error'])) {
927                return new DataResponse($result);
928            } else {
929                return new DataResponse($result['error'], 400);
930            }
931        } else {
932            return new DataResponse(
933                ['message' => $this->trans->t('You are not allowed to add members')],
934                403
935            );
936        }
937    }
938
939    /**
940     * @NoAdminRequired
941     *
942     * @param string $projectid
943     * @param int|null $lastchanged
944     * @param int|null $offset
945     * @param int|null $limit
946     * @param bool $reverse
947     * @param int|null $payerId
948     * @param int|null $categoryId
949     * @return DataResponse
950     * @throws \OCP\DB\Exception
951     */
952    public function webGetBills(string $projectid, ?int $lastchanged = null, ?int $offset = 0, ?int $limit = null,
953                                bool $reverse = false, ?int $payerId = null, ?int $categoryId = null,
954                                ?int $paymentModeId = null, ?int $includeBillId = null, ?string $searchTerm = null): DataResponse {
955        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
956            if ($limit) {
957                $bills = $this->projectService->getBillsWithLimit(
958                    $projectid, null, null, null, $paymentModeId, $categoryId, null, null,
959                    $lastchanged, $limit, $reverse, $offset, $payerId, $includeBillId, $searchTerm
960                );
961            } else {
962                $bills = $this->projectService->getBills(
963                    $projectid, null, null, null, $paymentModeId, $categoryId, null, null,
964                    $lastchanged, null, $reverse, $payerId
965                );
966            }
967            $result = [
968                'nb_bills' => $this->projectService->getNbBills($projectid, $payerId, $categoryId, $paymentModeId),
969                'bills' => $bills,
970            ];
971            return new DataResponse($result);
972        } else {
973            return new DataResponse(
974                ['message' => $this->trans->t('You are not allowed to get the bill list')],
975                403
976            );
977        }
978    }
979
980    /**
981     * @NoAdminRequired
982     * @NoCSRFRequired
983     *
984     */
985    public function webGetProjects(): DataResponse {
986        return new DataResponse(
987            $this->projectService->getProjects($this->userId)
988        );
989    }
990
991    /**
992     * @NoAdminRequired
993     * @NoCSRFRequired
994     *
995     */
996    public function webGetProjects2(): DataResponse {
997        return new DataResponse(
998            $this->projectService->getProjects($this->userId)
999        );
1000    }
1001
1002    /**
1003     * @NoAdminRequired
1004     * @NoCSRFRequired
1005     * @PublicPage
1006     * @CORS
1007     */
1008    public function apiCreateProject(string $name, string $id, ?string $password = null, ?string $contact_email = null): DataResponse {
1009        $allow = (int) $this->config->getAppValue('cospend', 'allowAnonymousCreation', '0');
1010        if ($allow) {
1011            $result = $this->projectService->createProject($name, $id, $password, $contact_email);
1012            if (isset($result['id'])) {
1013                return new DataResponse($result['id']);
1014            } else {
1015                return new DataResponse($result, 400);
1016            }
1017        } else {
1018            return new DataResponse(
1019                ['message' => $this->trans->t('Anonymous project creation is not allowed on this server')],
1020                403
1021            );
1022        }
1023    }
1024
1025    /**
1026     * @NoAdminRequired
1027     * @NoCSRFRequired
1028     * @CORS
1029     */
1030    public function apiPrivCreateProject(string $name, string $id, ?string $password = null, ?string $contact_email = null): DataResponse {
1031        $result = $this->projectService->createProject($name, $id, $password, $contact_email, $this->userId);
1032        if (isset($result['id'])) {
1033            return new DataResponse($result['id']);
1034        } else {
1035            return new DataResponse($result, 400);
1036        }
1037    }
1038
1039    /**
1040     * @NoAdminRequired
1041     * @NoCSRFRequired
1042     * @PublicPage
1043     * @CORS
1044     */
1045    public function apiGetProjectInfo(string $projectid, string $password): DataResponse {
1046        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1047        if ($this->checkLogin($projectid, $password)
1048            || ($publicShareInfo !== null
1049                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
1050        ) {
1051            $projectInfo = $this->projectService->getProjectInfo($publicShareInfo['projectid'] ?? $projectid);
1052            if ($projectInfo !== null) {
1053                unset($projectInfo['userid']);
1054                // for public link share: set the visible access level for frontend
1055                if ($publicShareInfo !== null) {
1056                    $projectInfo['myaccesslevel'] = $publicShareInfo['accesslevel'];
1057                } else {
1058                    // my access level is the guest one
1059                    $projectInfo['myaccesslevel'] = $projectInfo['guestaccesslevel'];
1060                }
1061                return new DataResponse($projectInfo);
1062            } else {
1063                return new DataResponse(
1064                    ['message' => $this->trans->t('Project not found')],
1065                    404
1066                );
1067            }
1068        } else {
1069            return new DataResponse(
1070                ['message' => $this->trans->t('Bad password or share link')],
1071                400
1072            );
1073        }
1074    }
1075
1076    /**
1077     * @NoAdminRequired
1078     * @NoCSRFRequired
1079     * @CORS
1080     */
1081    public function apiPrivGetProjectInfo(string $projectid): DataResponse {
1082        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
1083            $projectInfo = $this->projectService->getProjectInfo($projectid);
1084            if ($projectInfo !== null) {
1085                unset($projectInfo['userid']);
1086                $projectInfo['myaccesslevel'] = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
1087                return new DataResponse($projectInfo);
1088            } else {
1089                return new DataResponse(
1090                    ['message' => $this->trans->t('Project not found')],
1091                    404
1092                );
1093            }
1094        } else {
1095            return new DataResponse(
1096                ['message' => $this->trans->t('Unauthorized action')],
1097                401
1098            );
1099        }
1100    }
1101
1102    /**
1103     * @NoAdminRequired
1104     * @NoCSRFRequired
1105     * @PublicPage
1106     * @CORS
1107     */
1108    public function apiSetProjectInfo(string $projectid, string $passwd, ?string $name = null, ?string $contact_email = null,
1109                                    ?string $password = null, ?string $autoexport = null, ?string $currencyname = null,
1110                                    ?bool $deletion_disabled = null, ?string $categorysort = null, ?string $paymentmodesort = null): DataResponse {
1111        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1112        if (
1113            ($this->checkLogin($projectid, $passwd) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['admin'])
1114            || ($publicShareInfo !== null
1115                && (is_null($publicShareInfo['password']) || $passwd === $publicShareInfo['password'])
1116                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['admin'])
1117        ) {
1118            $result = $this->projectService->editProject(
1119                $publicShareInfo['projectid'] ?? $projectid, $name, $contact_email, $password, $autoexport,
1120                $currencyname, $deletion_disabled, $categorysort, $paymentmodesort
1121            );
1122            if (isset($result['success'])) {
1123                return new DataResponse('UPDATED');
1124            } else {
1125                return new DataResponse($result, 400);
1126            }
1127        } else {
1128            return new DataResponse(
1129                ['message' => $this->trans->t('Unauthorized action')],
1130                401
1131            );
1132        }
1133    }
1134
1135    /**
1136     * @NoAdminRequired
1137     * @NoCSRFRequired
1138     * @CORS
1139     */
1140    public function apiPrivSetProjectInfo(string $projectid, ?string $name = null, ?string $contact_email = null, ?string $password = null,
1141                                        ?string $autoexport = null, ?string $currencyname = null, ?bool $deletion_disabled = null,
1142                                        ?string $categorysort = null, ?string $paymentmodesort = null): DataResponse {
1143        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['admin']) {
1144            $result = $this->projectService->editProject(
1145                $projectid, $name, $contact_email, $password, $autoexport,
1146                $currencyname, $deletion_disabled, $categorysort, $paymentmodesort
1147            );
1148            if (isset($result['success'])) {
1149                return new DataResponse('UPDATED');
1150            } else {
1151                return new DataResponse($result, 400);
1152            }
1153        } else {
1154            return new DataResponse(
1155                ['message' => $this->trans->t('Unauthorized action')],
1156                401
1157            );
1158        }
1159    }
1160
1161    /**
1162     * @NoAdminRequired
1163     * @NoCSRFRequired
1164     * @PublicPage
1165     * @CORS
1166     */
1167    public function apiGetMembers(string $projectid, string $password, ?int $lastchanged = null): DataResponse {
1168        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1169        if ($this->checkLogin($projectid, $password)
1170            || ($publicShareInfo !== null
1171                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
1172        ) {
1173            $members = $this->projectService->getMembers($publicShareInfo['projectid'] ?? $projectid, null, $lastchanged);
1174            return new DataResponse($members);
1175        } else {
1176            return new DataResponse(
1177                ['message' => $this->trans->t('Unauthorized action')],
1178                401
1179            );
1180        }
1181    }
1182
1183    /**
1184     * @NoAdminRequired
1185     * @NoCSRFRequired
1186     * @CORS
1187     */
1188    public function apiPrivGetMembers(string $projectid, ?int $lastchanged = null): DataResponse {
1189        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
1190            $members = $this->projectService->getMembers($projectid, null, $lastchanged);
1191            return new DataResponse($members);
1192        } else {
1193            return new DataResponse(
1194                ['message' => $this->trans->t('Unauthorized action')],
1195                403
1196            );
1197        }
1198    }
1199
1200    /**
1201     * @NoAdminRequired
1202     * @NoCSRFRequired
1203     * @PublicPage
1204     * @CORS
1205     */
1206    public function apiGetBills(string $projectid, string $password, ?int $lastchanged = null,
1207                                ?int $offset = 0, ?int $limit = null, bool $reverse = false): DataResponse {
1208        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1209        if ($this->checkLogin($projectid, $password)
1210            || ($publicShareInfo !== null
1211                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
1212        ) {
1213            if ($limit) {
1214                $bills = $this->projectService->getBillsWithLimit(
1215                    $publicShareInfo['projectid'] ?? $projectid, null, null,
1216                    null, null, null, null, null,
1217                    $lastchanged, $limit, $reverse, $offset
1218                );
1219            } else {
1220                $bills = $this->projectService->getBills(
1221                    $publicShareInfo['projectid'] ?? $projectid, null, null,
1222                    null, null, null, null, null,
1223                    $lastchanged, null, $reverse
1224                );
1225            }
1226            return new DataResponse($bills);
1227        } else {
1228            return new DataResponse(
1229                ['message' => $this->trans->t('Unauthorized action')],
1230                401
1231            );
1232        }
1233    }
1234
1235    /**
1236     * @NoAdminRequired
1237     * @NoCSRFRequired
1238     * @PublicPage
1239     * @CORS
1240     *
1241     * @param string $projectid
1242     * @param string $password
1243     * @param int|null $lastchanged
1244     * @param int|null $offset
1245     * @param int|null $limit
1246     * @param bool $reverse
1247     * @param int|null $payerId
1248     * @return DataResponse
1249     */
1250    public function apiv3GetBills(string $projectid, string $password, ?int $lastchanged = null,
1251                                ?int $offset = 0, ?int $limit = null, bool $reverse = false,
1252                                ?int $payerId = null, ?int $categoryId = null,
1253                                ?int $paymentModeId = null, ?int $includeBillId = null, ?string $searchTerm = null): DataResponse {
1254        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1255        if ($this->checkLogin($projectid, $password)
1256            || ($publicShareInfo !== null
1257                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
1258        ) {
1259            if ($limit) {
1260                $bills = $this->projectService->getBillsWithLimit(
1261                    $publicShareInfo['projectid'] ?? $projectid, null, null,
1262                    null, $paymentModeId, $categoryId, null, null,
1263                    $lastchanged, $limit, $reverse, $offset, $payerId, $includeBillId, $searchTerm
1264                );
1265            } else {
1266                $bills = $this->projectService->getBills(
1267                    $publicShareInfo['projectid'] ?? $projectid, null, null,
1268                    null, $paymentModeId, $categoryId, null, null,
1269                    $lastchanged, null, $reverse, $payerId
1270                );
1271            }
1272            $result = [
1273                'nb_bills' => $this->projectService->getNbBills($publicShareInfo['projectid'] ?? $projectid, $payerId, $categoryId, $paymentModeId),
1274                'bills' => $bills,
1275            ];
1276            return new DataResponse($result);
1277        } else {
1278            return new DataResponse(
1279                ['message' => $this->trans->t('Unauthorized action')],
1280                401
1281            );
1282        }
1283    }
1284
1285    /**
1286     * @NoAdminRequired
1287     * @NoCSRFRequired
1288     * @CORS
1289     */
1290    public function apiPrivGetBills(string $projectid, ?int $lastchanged = null): DataResponse {
1291        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
1292            $bills = $this->projectService->getBills($projectid, null, null, null, null, null, null, null, $lastchanged);
1293            $billIds = $this->projectService->getAllBillIds($projectid);
1294            $ts = (new DateTime())->getTimestamp();
1295            return new DataResponse([
1296                'bills' => $bills,
1297                'allBillIds' => $billIds,
1298                'timestamp' => $ts,
1299            ]);
1300        } else {
1301            return new DataResponse(
1302                ['message' => $this->trans->t('Unauthorized action')],
1303                403
1304            );
1305        }
1306    }
1307
1308    /**
1309     * @NoAdminRequired
1310     * @NoCSRFRequired
1311     * @PublicPage
1312     * @CORS
1313     */
1314    public function apiv2GetBills(string $projectid, string $password, ?int $lastchanged = null): DataResponse {
1315        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1316        if ($this->checkLogin($projectid, $password)
1317            || ($publicShareInfo !== null
1318                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
1319        ) {
1320            $bills = $this->projectService->getBills(
1321                $publicShareInfo['projectid'] ?? $projectid, null, null,
1322                null, null, null, null, null, $lastchanged
1323            );
1324            $billIds = $this->projectService->getAllBillIds($publicShareInfo['projectid'] ?? $projectid);
1325            $ts = (new DateTime())->getTimestamp();
1326            return new DataResponse([
1327                'bills' => $bills,
1328                'allBillIds' => $billIds,
1329                'timestamp' => $ts,
1330            ]);
1331        } else {
1332            return new DataResponse(
1333                ['message' => $this->trans->t('Unauthorized action')],
1334                401
1335            );
1336        }
1337    }
1338
1339    /**
1340     * @NoAdminRequired
1341     * @NoCSRFRequired
1342     * @PublicPage
1343     * @CORS
1344     */
1345    public function apiAddMember(string $projectid, string $password, string $name,
1346                                float $weight = 1, int $active = 1, ?string $color = null): DataResponse {
1347        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1348        if (
1349            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
1350            || ($publicShareInfo !== null
1351                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1352                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
1353        ) {
1354            $result = $this->projectService->addMember(
1355                $publicShareInfo['projectid'] ?? $projectid, $name, $weight, $active !== 0, $color, null
1356            );
1357            if (!isset($result['error'])) {
1358                return new DataResponse($result['id']);
1359            } else {
1360                return new DataResponse($result['error'], 400);
1361            }
1362        } else {
1363            return new DataResponse(
1364                ['message' => $this->trans->t('You are not allowed to add members')],
1365                401
1366            );
1367        }
1368    }
1369
1370    /**
1371     * @NoAdminRequired
1372     * @NoCSRFRequired
1373     * @PublicPage
1374     * @CORS
1375     */
1376    public function apiv2AddMember(string $projectid, string $password, string $name, float $weight = 1, int $active = 1,
1377                                    ?string $color = null, ?string $userid = null): DataResponse {
1378        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1379        if (
1380            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
1381            || ($publicShareInfo !== null
1382                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1383                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
1384        ) {
1385            $result = $this->projectService->addMember(
1386                $publicShareInfo['projectid'] ?? $projectid, $name, $weight, $active !== 0, $color, $userid
1387            );
1388            if (!isset($result['error'])) {
1389                return new DataResponse($result);
1390            } else {
1391                return new DataResponse($result['error'], 400);
1392            }
1393        } else {
1394            return new DataResponse(
1395                ['message' => $this->trans->t('You are not allowed to add members')],
1396                401
1397            );
1398        }
1399    }
1400
1401    /**
1402     * @NoAdminRequired
1403     * @NoCSRFRequired
1404     * @CORS
1405     */
1406    public function apiPrivAddMember(string $projectid, string $name, float $weight = 1, int $active = 1,
1407                                    ?string $color = null, ?string $userid = null): DataResponse {
1408        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
1409            $result = $this->projectService->addMember($projectid, $name, $weight, $active !== 0, $color, $userid);
1410            if (!isset($result['error'])) {
1411                return new DataResponse($result['id']);
1412            } else {
1413                return new DataResponse($result['error'], 400);
1414            }
1415        } else {
1416            return new DataResponse(
1417                ['message' => $this->trans->t('You are not allowed to add members')],
1418                403
1419            );
1420        }
1421    }
1422
1423    /**
1424     * @NoAdminRequired
1425     * @NoCSRFRequired
1426     * @PublicPage
1427     * @CORS
1428     */
1429    public function apiAddBill(string $projectid, string $password, ?string $date = null, ?string $what = null, ?int $payer = null,
1430                            ?string $payed_for = null, ?float $amount = null, string $repeat = 'n',
1431                            ?string $paymentmode = null, ?int $paymentmodeid = null,
1432                            ?int $categoryid = null, int $repeatallactive = 0, ?string $repeatuntil = null, ?int $timestamp = null,
1433                            ?string $comment = null, ?int $repeatfreq = null): DataResponse {
1434        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1435        if (
1436            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
1437            || ($publicShareInfo !== null
1438                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1439                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
1440        ) {
1441            $result = $this->projectService->addBill(
1442                $publicShareInfo['projectid'] ?? $projectid, $date, $what, $payer, $payed_for, $amount,
1443                $repeat, $paymentmode, $paymentmodeid, $categoryid, $repeatallactive,
1444                $repeatuntil, $timestamp, $comment, $repeatfreq
1445            );
1446            if (isset($result['inserted_id'])) {
1447                $billObj = $this->billMapper->find($result['inserted_id']);
1448                if (is_null($publicShareInfo)) {
1449                    $authorFullText = $this->trans->t('Guest access');
1450                } elseif ($publicShareInfo['label']) {
1451                    $authorName = $publicShareInfo['label'];
1452                    $authorFullText = $this->trans->t('Share link (%s)', [$authorName]);
1453                } else {
1454                    $authorFullText = $this->trans->t('Share link');
1455                }
1456                $this->activityManager->triggerEvent(
1457                    ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1458                    ActivityManager::SUBJECT_BILL_CREATE,
1459                    ['author' => $authorFullText]
1460                );
1461                return new DataResponse($result['inserted_id']);
1462            } else {
1463                return new DataResponse($result, 400);
1464            }
1465        } else {
1466            return new DataResponse(
1467                ['message' => $this->trans->t('You are not allowed to add bills')],
1468                401
1469            );
1470        }
1471    }
1472
1473    /**
1474     * @NoAdminRequired
1475     * @NoCSRFRequired
1476     * @CORS
1477     */
1478    public function apiPrivAddBill(string $projectid, ?string $date = null, ?string $what = null, ?int $payer = null,
1479                                ?string $payed_for = null, ?float $amount = null, string $repeat = 'n',
1480                                ?string $paymentmode = null, ?int $paymentmodeid = null,
1481                                ?int $categoryid = null, int $repeatallactive = 0, ?string $repeatuntil = null, ?int $timestamp = null,
1482                                ?string $comment = null, ?int $repeatfreq = null): DataResponse {
1483        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
1484            $result = $this->projectService->addBill($projectid, $date, $what, $payer, $payed_for, $amount,
1485                                                     $repeat, $paymentmode, $paymentmodeid, $categoryid, $repeatallactive,
1486                                                     $repeatuntil, $timestamp, $comment, $repeatfreq);
1487            if (isset($result['inserted_id'])) {
1488                $billObj = $this->billMapper->find($result['inserted_id']);
1489                $this->activityManager->triggerEvent(
1490                    ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1491                    ActivityManager::SUBJECT_BILL_CREATE,
1492                    []
1493                );
1494                return new DataResponse($result['inserted_id']);
1495            } else {
1496                return new DataResponse($result, 400);
1497            }
1498        } else {
1499            return new DataResponse(
1500                ['message' => $this->trans->t('You are not allowed to add bills')],
1501                403
1502            );
1503        }
1504    }
1505
1506    /**
1507     * @NoAdminRequired
1508     * @NoCSRFRequired
1509     * @PublicPage
1510     * @CORS
1511     */
1512    public function apiRepeatBill(string $projectid, string $password, int $billid): DataResponse {
1513        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1514        if (
1515            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
1516            || ($publicShareInfo !== null
1517                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1518                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
1519        ) {
1520            // TODO check if bill is in this project
1521            $result = $this->projectService->cronRepeatBills($billid);
1522            return new DataResponse($result);
1523        } else {
1524            return new DataResponse(
1525                ['message' => $this->trans->t('You are not allowed to add bills')],
1526                401
1527            );
1528        }
1529    }
1530
1531    /**
1532     * @NoAdminRequired
1533     * @NoCSRFRequired
1534     * @PublicPage
1535     * @CORS
1536     */
1537    public function apiEditBill(string $projectid, string $password, int $billid, ?string $date = null, ?string $what = null,
1538                                ?int $payer = null, ?string $payed_for = null, ?float $amount = null, string $repeat = 'n',
1539                                ?string $paymentmode = null, ?int $paymentmodeid = null,
1540                                ?int $categoryid = null, ?int $repeatallactive = null,
1541                                ?string $repeatuntil = null, ?int $timestamp = null, ?string $comment = null,
1542                                ?int $repeatfreq = null): DataResponse {
1543        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1544        if (
1545            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
1546            || ($publicShareInfo !== null
1547                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1548                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
1549        ) {
1550            $result = $this->projectService->editBill(
1551                $publicShareInfo['projectid'] ?? $projectid, $billid, $date, $what, $payer, $payed_for,
1552                $amount, $repeat, $paymentmode, $paymentmodeid, $categoryid,
1553                $repeatallactive, $repeatuntil, $timestamp, $comment, $repeatfreq
1554            );
1555            if (isset($result['edited_bill_id'])) {
1556                $billObj = $this->billMapper->find($billid);
1557                if (is_null($publicShareInfo)) {
1558                    $authorFullText = $this->trans->t('Guest access');
1559                } elseif ($publicShareInfo['label']) {
1560                    $authorName = $publicShareInfo['label'];
1561                    $authorFullText = $this->trans->t('Share link (%s)', [$authorName]);
1562                } else {
1563                    $authorFullText = $this->trans->t('Share link');
1564                }
1565                $this->activityManager->triggerEvent(
1566                    ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1567                    ActivityManager::SUBJECT_BILL_UPDATE,
1568                    ['author' => $authorFullText]
1569                );
1570
1571                return new DataResponse($result['edited_bill_id']);
1572            } else {
1573                return new DataResponse($result, 400);
1574            }
1575        } else {
1576            return new DataResponse(
1577                ['message' => $this->trans->t('You are not allowed to edit this bill')],
1578                401
1579            );
1580        }
1581    }
1582
1583    /**
1584     * @NoAdminRequired
1585     * @NoCSRFRequired
1586     * @PublicPage
1587     * @CORS
1588     */
1589    public function apiEditBills(string $projectid, string $password, array $billIds, ?int $categoryid = null, ?string $date = null,
1590                                ?string $what = null, ?int $payer = null, ?string $payed_for = null, ?float $amount = null,
1591                                ?string $repeat = 'n', ?string $paymentmode = null, ?int $paymentmodeid = null,
1592                                ?int $repeatallactive = null,
1593                                ?string $repeatuntil = null, ?int $timestamp = null, ?string $comment = null,
1594                                ?int $repeatfreq = null): DataResponse {
1595        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1596        if (
1597            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
1598            || ($publicShareInfo !== null
1599                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1600                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
1601        ) {
1602            if (is_null($publicShareInfo)) {
1603                $authorFullText = $this->trans->t('Guest access');
1604            } elseif ($publicShareInfo['label']) {
1605                $authorName = $publicShareInfo['label'];
1606                $authorFullText = $this->trans->t('Share link (%s)', [$authorName]);
1607            } else {
1608                $authorFullText = $this->trans->t('Share link');
1609            }
1610            $paymentModes = $this->projectService->getCategoriesOrPaymentModes($publicShareInfo['projectid'] ?? $projectid, false);
1611            foreach ($billIds as $billid) {
1612                $result = $this->projectService->editBill(
1613                    $publicShareInfo['projectid'] ?? $projectid, $billid, $date, $what, $payer, $payed_for,
1614                    $amount, $repeat, $paymentmode, $paymentmodeid, $categoryid,
1615                    $repeatallactive, $repeatuntil, $timestamp, $comment, $repeatfreq, $paymentModes
1616                );
1617                if (isset($result['edited_bill_id'])) {
1618                    $billObj = $this->billMapper->find($billid);
1619                    $this->activityManager->triggerEvent(
1620                        ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1621                        ActivityManager::SUBJECT_BILL_UPDATE,
1622                        ['author' => $authorFullText]
1623                    );
1624                } else {
1625                    return new DataResponse($result, 400);
1626                }
1627            }
1628            return new DataResponse($billIds);
1629        } else {
1630            return new DataResponse(
1631                ['message' => $this->trans->t('You are not allowed to edit this bill')],
1632                401
1633            );
1634        }
1635    }
1636
1637    /**
1638     * @NoAdminRequired
1639     * @NoCSRFRequired
1640     * @CORS
1641     */
1642    public function apiPrivEditBill(string $projectid, int $billid, ?string $date = null, ?string $what = null,
1643                                ?int $payer = null, ?string $payed_for = null, ?float $amount = null, ?string $repeat = 'n',
1644                                ?string $paymentmode = null, ?int $paymentmodeid = null,
1645                                ?int $categoryid = null, ?int $repeatallactive = null,
1646                                ?string $repeatuntil = null, ?int $timestamp = null, ?string $comment=null,
1647                                ?int $repeatfreq = null): DataResponse {
1648        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
1649            $result = $this->projectService->editBill($projectid, $billid, $date, $what, $payer, $payed_for,
1650                                                      $amount, $repeat, $paymentmode, $paymentmodeid, $categoryid,
1651                                                      $repeatallactive, $repeatuntil, $timestamp, $comment, $repeatfreq);
1652            if (isset($result['edited_bill_id'])) {
1653                $billObj = $this->billMapper->find($billid);
1654                $this->activityManager->triggerEvent(
1655                    ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1656                    ActivityManager::SUBJECT_BILL_UPDATE,
1657                    []
1658                );
1659
1660                return new DataResponse($result['edited_bill_id']);
1661            } else {
1662                return new DataResponse($result, 400);
1663            }
1664        } else {
1665            return new DataResponse(
1666                ['message' => $this->trans->t('Unauthorized action')],
1667                403
1668            );
1669        }
1670    }
1671
1672    /**
1673     * @NoAdminRequired
1674     * @NoCSRFRequired
1675     * @PublicPage
1676     * @CORS
1677     */
1678    public function apiDeleteBill(string $projectid, string $password, int $billid): DataResponse {
1679        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1680        if (
1681            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
1682            || ($publicShareInfo !== null
1683                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1684                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
1685        ) {
1686            $billObj = null;
1687            if ($this->projectService->getBill($publicShareInfo['projectid'] ?? $projectid, $billid) !== null) {
1688                $billObj = $this->billMapper->find($billid);
1689            }
1690
1691            $result = $this->projectService->deleteBill($publicShareInfo['projectid'] ?? $projectid, $billid);
1692            if (isset($result['success'])) {
1693                if (!is_null($billObj)) {
1694                    if (is_null($publicShareInfo)) {
1695                        $authorFullText = $this->trans->t('Guest access');
1696                    } elseif ($publicShareInfo['label']) {
1697                        $authorName = $publicShareInfo['label'];
1698                        $authorFullText = $this->trans->t('Share link (%s)', [$authorName]);
1699                    } else {
1700                        $authorFullText = $this->trans->t('Share link');
1701                    }
1702                    $this->activityManager->triggerEvent(
1703                        ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1704                        ActivityManager::SUBJECT_BILL_DELETE,
1705                        ['author' => $authorFullText]
1706                    );
1707                }
1708                return new DataResponse('OK');
1709            } else {
1710                return new DataResponse($result, 404);
1711            }
1712        } else {
1713            return new DataResponse(
1714                ['message' => $this->trans->t('Unauthorized action')],
1715                401
1716            );
1717        }
1718    }
1719
1720    /**
1721     * @NoAdminRequired
1722     * @NoCSRFRequired
1723     * @PublicPage
1724     * @CORS
1725     */
1726    public function apiDeleteBills(string $projectid, string $password, array $billIds): DataResponse {
1727        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1728        if (
1729            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
1730            || ($publicShareInfo !== null
1731                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1732                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
1733        ) {
1734            if (is_null($publicShareInfo)) {
1735                $authorFullText = $this->trans->t('Guest access');
1736            } elseif ($publicShareInfo['label']) {
1737                $authorName = $publicShareInfo['label'];
1738                $authorFullText = $this->trans->t('Share link (%s)', [$authorName]);
1739            } else {
1740                $authorFullText = $this->trans->t('Share link');
1741            }
1742            foreach ($billIds as $billid) {
1743                $billObj = null;
1744                if ($this->projectService->getBill($publicShareInfo['projectid'] ?? $projectid, $billid) !== null) {
1745                    $billObj = $this->billMapper->find($billid);
1746                }
1747
1748                $result = $this->projectService->deleteBill($publicShareInfo['projectid'] ?? $projectid, $billid);
1749                if (!isset($result['success'])) {
1750                    return new DataResponse($result, 404);
1751                } else {
1752                    if (!is_null($billObj)) {
1753                        $this->activityManager->triggerEvent(
1754                            ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1755                            ActivityManager::SUBJECT_BILL_DELETE,
1756                            ['author' => $authorFullText]
1757                        );
1758                    }
1759                }
1760            }
1761            return new DataResponse('OK');
1762        } else {
1763            return new DataResponse(
1764                ['message' => $this->trans->t('Unauthorized action')],
1765                401
1766            );
1767        }
1768    }
1769
1770    /**
1771     * @NoAdminRequired
1772     * @NoCSRFRequired
1773     * @CORS
1774     */
1775    public function apiPrivDeleteBill(string $projectid, int $billid): DataResponse {
1776        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
1777            $billObj = null;
1778            if ($this->projectService->getBill($projectid, $billid) !== null) {
1779                $billObj = $this->billMapper->find($billid);
1780            }
1781
1782            $result = $this->projectService->deleteBill($projectid, $billid);
1783            if (isset($result['success'])) {
1784                if (!is_null($billObj)) {
1785                    $this->activityManager->triggerEvent(
1786                        ActivityManager::COSPEND_OBJECT_BILL, $billObj,
1787                        ActivityManager::SUBJECT_BILL_DELETE,
1788                        []
1789                    );
1790                }
1791                return new DataResponse('OK');
1792            } else {
1793                return new DataResponse($result, 404);
1794            }
1795        } else {
1796            return new DataResponse(
1797                ['message' => $this->trans->t('Unauthorized action')],
1798                403
1799            );
1800        }
1801    }
1802
1803    /**
1804     * @NoAdminRequired
1805     * @NoCSRFRequired
1806     * @PublicPage
1807     * @CORS
1808     */
1809    public function apiDeleteMember(string $projectid, string $password, int $memberid): DataResponse {
1810        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1811        if (
1812            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
1813            || ($publicShareInfo !== null
1814                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1815                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
1816        ) {
1817            $result = $this->projectService->deleteMember($publicShareInfo['projectid'] ?? $projectid, $memberid);
1818            if (isset($result['success'])) {
1819                return new DataResponse('OK');
1820            } else {
1821                return new DataResponse($result, 404);
1822            }
1823        } else {
1824            return new DataResponse(
1825                ['message' => $this->trans->t('Unauthorized action')],
1826                401
1827            );
1828        }
1829    }
1830
1831    /**
1832     * @NoAdminRequired
1833     * @NoCSRFRequired
1834     * @CORS
1835     */
1836    public function apiPrivDeleteMember(string $projectid, int $memberid): DataResponse {
1837        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
1838            $result = $this->projectService->deleteMember($projectid, $memberid);
1839            if (isset($result['success'])) {
1840                return new DataResponse('OK');
1841            } else {
1842                return new DataResponse($result, 404);
1843            }
1844        } else {
1845            return new DataResponse(
1846                ['message' => $this->trans->t('Unauthorized action')],
1847                403
1848            );
1849        }
1850    }
1851
1852    /**
1853     * @NoAdminRequired
1854     * @NoCSRFRequired
1855     * @PublicPage
1856     * @CORS
1857     */
1858    public function apiDeleteProject(string $projectid, string $password): DataResponse {
1859        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1860        if (
1861            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['admin'])
1862            || ($publicShareInfo !== null
1863                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1864                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['admin'])
1865        ) {
1866            $result = $this->projectService->deleteProject($publicShareInfo['projectid'] ?? $projectid);
1867            if (!isset($result['error'])) {
1868                return new DataResponse($result);
1869            } else {
1870                return new DataResponse(['message' => $result['error']], 404);
1871            }
1872        } else {
1873            return new DataResponse(
1874                ['message' => $this->trans->t('Unauthorized action')],
1875                401
1876            );
1877        }
1878    }
1879
1880    /**
1881     * @NoAdminRequired
1882     * @NoCSRFRequired
1883     * @CORS
1884     */
1885    public function apiPrivDeleteProject(string $projectid): DataResponse {
1886        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['admin']) {
1887            $result = $this->projectService->deleteProject($projectid);
1888            if (!isset($result['error'])) {
1889                return new DataResponse($result);
1890            } else {
1891                return new DataResponse(['message' => $result['error']], 404);
1892            }
1893        } else {
1894            return new DataResponse(
1895                ['message' => $this->trans->t('Unauthorized action')],
1896                403
1897            );
1898        }
1899    }
1900
1901    /**
1902     * @NoAdminRequired
1903     * @NoCSRFRequired
1904     * @PublicPage
1905     * @CORS
1906     */
1907    public function apiEditMember(string $projectid, string $password, int $memberid,
1908                                ?string $name = null, ?float $weight = null, $activated = null,
1909                                ?string $color = null, ?string $userid = null): DataResponse {
1910        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1911        if (
1912            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
1913            || ($publicShareInfo !== null
1914                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
1915                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
1916        ) {
1917            if ($activated === 'true') {
1918                $activated = true;
1919            } elseif ($activated === 'false') {
1920                $activated = false;
1921            }
1922            $result = $this->projectService->editMember(
1923                $publicShareInfo['projectid'] ?? $projectid, $memberid, $name, $userid, $weight, $activated, $color
1924            );
1925            if (count($result) === 0) {
1926                return new DataResponse(null);
1927            } elseif (array_key_exists('activated', $result)) {
1928                return new DataResponse($result);
1929            } else {
1930                return new DataResponse($result, 403);
1931            }
1932        } else {
1933            return new DataResponse(
1934                ['message' => $this->trans->t('Unauthorized action')],
1935                401
1936            );
1937        }
1938    }
1939
1940    /**
1941     * @NoAdminRequired
1942     * @NoCSRFRequired
1943     * @CORS
1944     */
1945    public function apiPrivEditMember(string $projectid, int $memberid, ?string $name = null, ?float $weight = null,
1946                                    $activated = null, ?string $color = null, ?string $userid = null): DataResponse {
1947        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
1948            if ($activated === 'true') {
1949                $activated = true;
1950            } elseif ($activated === 'false') {
1951                $activated = false;
1952            }
1953            $result = $this->projectService->editMember($projectid, $memberid, $name, $userid, $weight, $activated, $color);
1954            if (count($result) === 0) {
1955                return new DataResponse(null);
1956            } elseif (array_key_exists('activated', $result)) {
1957                return new DataResponse($result);
1958            } else {
1959                return new DataResponse($result, 403);
1960            }
1961        } else {
1962            return new DataResponse(
1963                ['message' => $this->trans->t('Unauthorized action')],
1964                403
1965            );
1966        }
1967    }
1968
1969    /**
1970     * @NoAdminRequired
1971     * @NoCSRFRequired
1972     * @PublicPage
1973     * @CORS
1974     */
1975    public function apiGetProjectStatistics(string $projectid, string $password, ?int $tsMin = null, ?int $tsMax = null,
1976                                            ?int   $paymentModeId = null, ?int $categoryId = null,
1977                                            ?float $amountMin = null, ?float $amountMax=null,
1978                                            string $showDisabled = '1', ?int $currencyId = null,
1979                                            ?int $payerId = null): DataResponse {
1980        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
1981        if ($this->checkLogin($projectid, $password)
1982            || ($publicShareInfo !== null
1983                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
1984        ) {
1985            $result = $this->projectService->getProjectStatistics(
1986                $publicShareInfo['projectid'] ?? $projectid, 'lowername', $tsMin, $tsMax,
1987                $paymentModeId, $categoryId, $amountMin, $amountMax, $showDisabled === '1', $currencyId,
1988                $payerId
1989            );
1990            return new DataResponse($result);
1991        } else {
1992            return new DataResponse(
1993                ['message' => $this->trans->t('Unauthorized action')],
1994                401
1995            );
1996        }
1997    }
1998
1999    /**
2000     * @NoAdminRequired
2001     * @NoCSRFRequired
2002     * @CORS
2003     */
2004    public function apiPrivGetProjectStatistics(string $projectid, ?int $tsMin = null, ?int $tsMax = null,
2005                                                ?int   $paymentModeId = null,
2006                                                ?int   $categoryId = null, ?float $amountMin = null, ?float $amountMax = null,
2007                                                string $showDisabled = '1', ?int $currencyId = null,
2008                                                ?int $payerId = null): DataResponse {
2009        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
2010            $result = $this->projectService->getProjectStatistics(
2011                $projectid, 'lowername', $tsMin, $tsMax, $paymentModeId,
2012                $categoryId, $amountMin, $amountMax, $showDisabled === '1', $currencyId, $payerId
2013            );
2014            return new DataResponse($result);
2015        } else {
2016            return new DataResponse(
2017                ['message' => $this->trans->t('Unauthorized action')],
2018                403
2019            );
2020        }
2021    }
2022
2023    /**
2024     * @NoAdminRequired
2025     * @NoCSRFRequired
2026     * @PublicPage
2027     * @CORS
2028     */
2029    public function apiGetProjectSettlement(string $projectid, string $password, ?int $centeredOn = null, ?int $maxTimestamp = null): DataResponse {
2030        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2031        if ($this->checkLogin($projectid, $password)
2032            || ($publicShareInfo !== null
2033                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password']))
2034        ) {
2035            $result = $this->projectService->getProjectSettlement(
2036                $publicShareInfo['projectid'] ?? $projectid, $centeredOn, $maxTimestamp
2037            );
2038            return new DataResponse($result);
2039        } else {
2040            return new DataResponse(
2041                ['message' => $this->trans->t('Unauthorized action')],
2042                401
2043            );
2044        }
2045    }
2046
2047    /**
2048     * @NoAdminRequired
2049     * @NoCSRFRequired
2050     * @CORS
2051     */
2052    public function apiPrivGetProjectSettlement(string $projectid, ?int $centeredOn = null, ?int $maxTimestamp = null): DataResponse {
2053        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
2054            $result = $this->projectService->getProjectSettlement($projectid, $centeredOn, $maxTimestamp);
2055            return new DataResponse($result);
2056        } else {
2057            return new DataResponse(
2058                ['message' => $this->trans->t('Unauthorized action')],
2059                403
2060            );
2061        }
2062    }
2063
2064    /**
2065     * @NoAdminRequired
2066     * @NoCSRFRequired
2067     * @PublicPage
2068     * @CORS
2069     */
2070    public function apiAutoSettlement(string $projectid, string $password, ?int $centeredOn = null,
2071                                    int $precision = 2, ?int $maxTimestamp = null): DataResponse {
2072        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2073        if (
2074            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['participant'])
2075            || ($publicShareInfo !== null
2076                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2077                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['participant'])
2078        ) {
2079            $result = $this->projectService->autoSettlement(
2080                $publicShareInfo['projectid'] ?? $projectid, $centeredOn, $precision, $maxTimestamp
2081            );
2082            if (isset($result['success'])) {
2083                return new DataResponse('OK');
2084            } else {
2085                return new DataResponse($result, 403);
2086            }
2087        } else {
2088            return new DataResponse(
2089                ['message' => $this->trans->t('Unauthorized action')],
2090                401
2091            );
2092        }
2093    }
2094
2095    /**
2096     * @NoAdminRequired
2097     * @NoCSRFRequired
2098     * @CORS
2099     */
2100    public function apiPrivAutoSettlement(string $projectid, ?int $centeredOn = null, int $precision = 2, ?int $maxTimestamp = null): DataResponse {
2101        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
2102            $result = $this->projectService->autoSettlement($projectid, $centeredOn, $precision, $maxTimestamp);
2103            if (isset($result['success'])) {
2104                return new DataResponse('OK');
2105            } else {
2106                return new DataResponse($result, 403);
2107            }
2108        } else {
2109            return new DataResponse(
2110                ['message' => $this->trans->t('Unauthorized action')],
2111                403
2112            );
2113        }
2114    }
2115
2116    /**
2117     * @NoAdminRequired
2118     */
2119    public function editShareAccessLevel(string $projectid, int $shid, int $accesslevel): DataResponse {
2120        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
2121        $shareAccessLevel = $this->projectService->getShareAccessLevel($projectid, $shid);
2122        // allow edition if user is at least participant and has greater or equal access level than target
2123        // user can't give higher access level than their level (do not downgrade one)
2124        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant'] && $userAccessLevel >= $accesslevel && $userAccessLevel >= $shareAccessLevel) {
2125            $result = $this->projectService->editShareAccessLevel($projectid, $shid, $accesslevel);
2126            if (isset($result['success'])) {
2127                return new DataResponse('OK');
2128            } else {
2129                return new DataResponse($result, 400);
2130            }
2131        } else {
2132            return new DataResponse(
2133                ['message' => $this->trans->t('You are not allowed to give such shared access level')],
2134                403
2135            );
2136        }
2137    }
2138
2139    /**
2140     * @NoAdminRequired
2141     */
2142    public function editShareAccess(string $projectid, int $shid, ?string $label = null, ?string $password = null): DataResponse {
2143        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
2144        $shareAccessLevel = $this->projectService->getShareAccessLevel($projectid, $shid);
2145        // allow edition if user is at least participant and has greater or equal access level than target
2146        // user can't give higher access level than their level (do not downgrade one)
2147        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant'] && $userAccessLevel >= $shareAccessLevel) {
2148            $result = $this->projectService->editShareAccess($projectid, $shid, $label, $password);
2149            if (isset($result['success'])) {
2150                return new DataResponse('OK');
2151            } else {
2152                return new DataResponse($result, 400);
2153            }
2154        } else {
2155            return new DataResponse(
2156                ['message' => $this->trans->t('You are not allowed to edit this shared access')],
2157                403
2158            );
2159        }
2160    }
2161
2162    /**
2163     * @NoAdminRequired
2164     */
2165    public function editGuestAccessLevel(string $projectid, int $accesslevel): DataResponse {
2166        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
2167        if ($userAccessLevel >= Application::ACCESS_LEVELS['admin']) {
2168            $result = $this->projectService->editGuestAccessLevel($projectid, $accesslevel);
2169            if (isset($result['success'])) {
2170                return new DataResponse('OK');
2171            } else {
2172                return new DataResponse($result, 400);
2173            }
2174        } else {
2175            return new DataResponse(
2176                ['message' => $this->trans->t('You are not allowed to edit guest access level')],
2177                403
2178            );
2179        }
2180    }
2181
2182    /**
2183     * @NoAdminRequired
2184     * @NoCSRFRequired
2185     * @PublicPage
2186     * @CORS
2187     */
2188    public function apiEditGuestAccessLevel($projectid, $password, $accesslevel): DataResponse {
2189        return new DataResponse(
2190            ['message' => $this->trans->t('You are not allowed to edit guest access level')],
2191            403
2192        );
2193        //if ($this->checkLogin($projectid, $password)) {
2194        //    $guestAccessLevel = $this->projectService->getGuestAccessLevel($projectid);
2195        //    if ($guestAccessLevel >= Application::ACCESS_LEVELS['participant'] and $guestAccessLevel >= $accesslevel) {
2196        //        $result = $this->projectService->editGuestAccessLevel($projectid, $accesslevel);
2197        //        if ($result === 'OK') {
2198        //            return new DataResponse($result);
2199        //        }
2200        //        else {
2201        //            return new DataResponse($result, 400);
2202        //        }
2203        //    }
2204        //    else {
2205        //        return new DataResponse(
2206        //            ['message' => $this->trans->t('You are not allowed to give such access level')],
2207        //                403
2208        //        );
2209        //    }
2210        //}
2211        //else {
2212        //    return new DataResponse(
2213        //        ['message' => $this->trans->t('You are not allowed to access this project')],
2214        //            403
2215        //    );
2216        //}
2217    }
2218
2219    /**
2220     * @NoAdminRequired
2221     */
2222    public function addPaymentMode(string $projectid, string $name, ?string $icon, string $color, ?int $order = 0): DataResponse {
2223        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2224            $result = $this->projectService->addPaymentMode($projectid, $name, $icon, $color, $order);
2225            if (is_numeric($result)) {
2226                return new DataResponse($result);
2227            } else {
2228                return new DataResponse($result, 400);
2229            }
2230        } else {
2231            return new DataResponse(
2232                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2233                403
2234            );
2235        }
2236    }
2237
2238    /**
2239     * @NoAdminRequired
2240     * @NoCSRFRequired
2241     * @PublicPage
2242     * @CORS
2243     */
2244    public function apiAddPaymentMode(string $projectid, string $password, string $name, ?string $icon, string $color, ?int $order = 0): DataResponse {
2245        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2246        if (
2247            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2248            || ($publicShareInfo !== null
2249                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2250                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2251        ) {
2252            $result = $this->projectService->addPaymentMode(
2253                $publicShareInfo['projectid'] ?? $projectid, $name, $icon, $color, $order
2254            );
2255            if (is_numeric($result)) {
2256                return new DataResponse($result);
2257            } else {
2258                return new DataResponse($result, 400);
2259            }
2260        } else {
2261            return new DataResponse(
2262                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2263                401
2264            );
2265        }
2266    }
2267
2268    /**
2269     * @NoAdminRequired
2270     * @NoCSRFRequired
2271     * @CORS
2272     */
2273    public function apiPrivAddPaymentMode(string $projectid, string $name, ?string $icon = null, ?string $color = null): DataResponse {
2274        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2275            $result = $this->projectService->addPaymentMode($projectid, $name, $icon, $color);
2276            if (is_numeric($result)) {
2277                return new DataResponse($result);
2278            } else {
2279                return new DataResponse($result, 400);
2280            }
2281        } else {
2282            return new DataResponse(
2283                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2284                403
2285            );
2286        }
2287    }
2288
2289    /**
2290     * @NoAdminRequired
2291     */
2292    public function editPaymentMode(string $projectid, int $pmid, ?string $name = null,
2293                                 ?string $icon = null, ?string $color = null): DataResponse {
2294        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2295            $result = $this->projectService->editPaymentMode($projectid, $pmid, $name, $icon, $color);
2296            if (is_array($result)) {
2297                return new DataResponse($result);
2298            } else {
2299                return new DataResponse($result, 400);
2300            }
2301        } else {
2302            return new DataResponse(
2303                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2304                403
2305            );
2306        }
2307    }
2308
2309    /**
2310     * @NoAdminRequired
2311     */
2312    public function savePaymentModeOrder(string $projectid, array $order): DataResponse {
2313        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2314            if ($this->projectService->savePaymentModeOrder($projectid, $order)) {
2315                return new DataResponse(true);
2316            } else {
2317                return new DataResponse(false, 400);
2318            }
2319        } else {
2320            return new DataResponse(
2321                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2322                403
2323            );
2324        }
2325    }
2326
2327    /**
2328     * @NoAdminRequired
2329     * @NoCSRFRequired
2330     * @PublicPage
2331     * @CORS
2332     */
2333    public function apiEditPaymentMode(string $projectid, string $password, int $pmid, ?string $name = null,
2334                                    ?string $icon = null, ?string $color = null): DataResponse {
2335        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2336        if (
2337            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2338            || ($publicShareInfo !== null
2339                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2340                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2341        ) {
2342            $result = $this->projectService->editPaymentMode(
2343                $publicShareInfo['projectid'] ?? $projectid, $pmid, $name, $icon, $color
2344            );
2345            if (is_array($result)) {
2346                return new DataResponse($result);
2347            } else {
2348                return new DataResponse($result, 403);
2349            }
2350        } else {
2351            return new DataResponse(
2352                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2353                401
2354            );
2355        }
2356    }
2357
2358    /**
2359     * @NoAdminRequired
2360     * @NoCSRFRequired
2361     * @PublicPage
2362     * @CORS
2363     */
2364    public function apiSavePaymentModeOrder(string $projectid, string $password, array $order): DataResponse {
2365        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2366        if (
2367            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2368            || ($publicShareInfo !== null
2369                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2370                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2371        ) {
2372            if ($this->projectService->savePaymentModeOrder($publicShareInfo['projectid'] ?? $projectid, $order)) {
2373                return new DataResponse(true);
2374            } else {
2375                return new DataResponse(false, 403);
2376            }
2377        } else {
2378            return new DataResponse(
2379                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2380                401
2381            );
2382        }
2383    }
2384
2385    /**
2386     * @NoAdminRequired
2387     * @NoCSRFRequired
2388     * @CORS
2389     */
2390    public function apiPrivEditPaymentMode(string $projectid, int $pmid, ?string $name = null,
2391                                        ?string $icon = null, ?string $color = null): DataResponse {
2392        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2393            $result = $this->projectService->editPaymentMode($projectid, $pmid, $name, $icon, $color);
2394            if (is_array($result)) {
2395                return new DataResponse($result);
2396            } else {
2397                return new DataResponse($result, 403);
2398            }
2399        } else {
2400            return new DataResponse(
2401                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2402                403
2403            );
2404        }
2405    }
2406
2407    /**
2408     * @NoAdminRequired
2409     */
2410    public function deletePaymentMode(string $projectid, int $pmid): DataResponse {
2411        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2412            $result = $this->projectService->deletePaymentMode($projectid, $pmid);
2413            if (isset($result['success'])) {
2414                return new DataResponse($pmid);
2415            } else {
2416                return new DataResponse($result, 400);
2417            }
2418        } else {
2419            return new DataResponse(
2420                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2421                403
2422            );
2423        }
2424    }
2425
2426    /**
2427     * @NoAdminRequired
2428     * @NoCSRFRequired
2429     * @PublicPage
2430     * @CORS
2431     */
2432    public function apiDeletePaymentMode(string $projectid, string $password, int $pmid): DataResponse {
2433        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2434        if (
2435            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2436            || ($publicShareInfo !== null
2437                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2438                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2439        ) {
2440            $result = $this->projectService->deletePaymentMode($publicShareInfo['projectid'] ?? $projectid, $pmid);
2441            if (isset($result['success'])) {
2442                return new DataResponse($pmid);
2443            } else {
2444                return new DataResponse($result, 400);
2445            }
2446        } else {
2447            return new DataResponse(
2448                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2449                401
2450            );
2451        }
2452    }
2453
2454    /**
2455     * @NoAdminRequired
2456     * @NoCSRFRequired
2457     * @CORS
2458     */
2459    public function apiPrivDeletePaymentMode(string $projectid, int $pmid): DataResponse {
2460        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2461            $result = $this->projectService->deletePaymentMode($projectid, $pmid);
2462            if (isset($result['success'])) {
2463                return new DataResponse($pmid);
2464            } else {
2465                return new DataResponse($result, 400);
2466            }
2467        } else {
2468            return new DataResponse(
2469                ['message' => $this->trans->t('You are not allowed to manage payment modes')],
2470                403
2471            );
2472        }
2473    }
2474
2475    /**
2476     * @NoAdminRequired
2477     */
2478    public function addCategory(string $projectid, string $name, ?string $icon, string $color, ?int $order = 0): DataResponse {
2479        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2480            $result = $this->projectService->addCategory($projectid, $name, $icon, $color, $order);
2481            if (is_numeric($result)) {
2482                return new DataResponse($result);
2483            } else {
2484                return new DataResponse($result, 400);
2485            }
2486        } else {
2487            return new DataResponse(
2488                ['message' => $this->trans->t('You are not allowed to manage categories')],
2489                403
2490            );
2491        }
2492    }
2493
2494    /**
2495     * @NoAdminRequired
2496     * @NoCSRFRequired
2497     * @PublicPage
2498     * @CORS
2499     */
2500    public function apiAddCategory(string $projectid, string $password, string $name, ?string $icon, string $color, ?int $order = 0): DataResponse {
2501        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2502        if (
2503            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2504            || ($publicShareInfo !== null
2505                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2506                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2507        ) {
2508            $result = $this->projectService->addCategory(
2509                $publicShareInfo['projectid'] ?? $projectid, $name, $icon, $color, $order
2510            );
2511            if (is_numeric($result)) {
2512                // inserted category id
2513                return new DataResponse($result);
2514            } else {
2515                return new DataResponse($result, 400);
2516            }
2517        } else {
2518            return new DataResponse(
2519                ['message' => $this->trans->t('You are not allowed to manage categories')],
2520                401
2521            );
2522        }
2523    }
2524
2525    /**
2526     * @NoAdminRequired
2527     * @NoCSRFRequired
2528     * @CORS
2529     */
2530    public function apiPrivAddCategory(string $projectid, string $name, ?string $icon = null, ?string $color = null): DataResponse {
2531        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2532            $result = $this->projectService->addCategory($projectid, $name, $icon, $color);
2533            if (is_numeric($result)) {
2534                // inserted category id
2535                return new DataResponse($result);
2536            } else {
2537                return new DataResponse($result, 400);
2538            }
2539        } else {
2540            return new DataResponse(
2541                ['message' => $this->trans->t('You are not allowed to manage categories')],
2542                403
2543            );
2544        }
2545    }
2546
2547    /**
2548     * @NoAdminRequired
2549     */
2550    public function editCategory(string $projectid, int $categoryid, ?string $name = null,
2551                                ?string $icon = null, ?string $color = null): DataResponse {
2552        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2553            $result = $this->projectService->editCategory($projectid, $categoryid, $name, $icon, $color);
2554            if (is_array($result)) {
2555                return new DataResponse($result);
2556            } else {
2557                return new DataResponse($result, 400);
2558            }
2559        } else {
2560            return new DataResponse(
2561                ['message' => $this->trans->t('You are not allowed to manage categories')],
2562                403
2563            );
2564        }
2565    }
2566
2567    /**
2568     * @NoAdminRequired
2569     */
2570    public function saveCategoryOrder(string $projectid, array $order): DataResponse {
2571        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2572            if ($this->projectService->saveCategoryOrder($projectid, $order)) {
2573                return new DataResponse(true);
2574            } else {
2575                return new DataResponse(false, 400);
2576            }
2577        } else {
2578            return new DataResponse(
2579                ['message' => $this->trans->t('You are not allowed to manage categories')],
2580                403
2581            );
2582        }
2583    }
2584
2585    /**
2586     * @NoAdminRequired
2587     * @NoCSRFRequired
2588     * @PublicPage
2589     * @CORS
2590     */
2591    public function apiEditCategory(string $projectid, string $password, int $categoryid, ?string $name = null,
2592                                    ?string $icon = null, ?string $color = null): DataResponse {
2593        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2594        if (
2595            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2596            || ($publicShareInfo !== null
2597                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2598                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2599        ) {
2600            $result = $this->projectService->editCategory(
2601                $publicShareInfo['projectid'] ?? $projectid, $categoryid, $name, $icon, $color
2602            );
2603            if (is_array($result)) {
2604                return new DataResponse($result);
2605            } else {
2606                return new DataResponse($result, 403);
2607            }
2608        } else {
2609            return new DataResponse(
2610                ['message' => $this->trans->t('You are not allowed to manage categories')],
2611                401
2612            );
2613        }
2614    }
2615
2616    /**
2617     * @NoAdminRequired
2618     * @NoCSRFRequired
2619     * @PublicPage
2620     * @CORS
2621     */
2622    public function apiSaveCategoryOrder(string $projectid, string $password, array $order): DataResponse {
2623        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2624        if (
2625            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2626            || ($publicShareInfo !== null
2627                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2628                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2629        ) {
2630            if ($this->projectService->saveCategoryOrder($publicShareInfo['projectid'] ?? $projectid, $order)) {
2631                return new DataResponse(true);
2632            } else {
2633                return new DataResponse(false, 403);
2634            }
2635        } else {
2636            return new DataResponse(
2637                ['message' => $this->trans->t('You are not allowed to manage categories')],
2638                401
2639            );
2640        }
2641    }
2642
2643    /**
2644     * @NoAdminRequired
2645     * @NoCSRFRequired
2646     * @CORS
2647     */
2648    public function apiPrivEditCategory(string $projectid, int $categoryid, ?string $name = null,
2649                                        ?string $icon = null, ?string $color = null): DataResponse {
2650        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2651            $result = $this->projectService->editCategory($projectid, $categoryid, $name, $icon, $color);
2652            if (is_array($result)) {
2653                return new DataResponse($result);
2654            } else {
2655                return new DataResponse($result, 403);
2656            }
2657        } else {
2658            return new DataResponse(
2659                ['message' => $this->trans->t('You are not allowed to manage categories')],
2660                403
2661            );
2662        }
2663    }
2664
2665    /**
2666     * @NoAdminRequired
2667     */
2668    public function deleteCategory(string $projectid, int $categoryid): DataResponse {
2669        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2670            $result = $this->projectService->deleteCategory($projectid, $categoryid);
2671            if (isset($result['success'])) {
2672                return new DataResponse($categoryid);
2673            } else {
2674                return new DataResponse($result, 400);
2675            }
2676        } else {
2677            return new DataResponse(
2678                ['message' => $this->trans->t('You are not allowed to manage categories')],
2679                403
2680            );
2681        }
2682    }
2683
2684    /**
2685     * @NoAdminRequired
2686     * @NoCSRFRequired
2687     * @PublicPage
2688     * @CORS
2689     */
2690    public function apiDeleteCategory(string $projectid, string $password, int $categoryid): DataResponse {
2691        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2692        if (
2693            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2694            || ($publicShareInfo !== null
2695                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2696                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2697        ) {
2698            $result = $this->projectService->deleteCategory($publicShareInfo['projectid'] ?? $projectid, $categoryid);
2699            if (isset($result['success'])) {
2700                return new DataResponse($categoryid);
2701            } else {
2702                return new DataResponse($result, 400);
2703            }
2704        } else {
2705            return new DataResponse(
2706                ['message' => $this->trans->t('You are not allowed to manage categories')],
2707                401
2708            );
2709        }
2710    }
2711
2712    /**
2713     * @NoAdminRequired
2714     * @NoCSRFRequired
2715     * @CORS
2716     */
2717    public function apiPrivDeleteCategory(string $projectid, int $categoryid): DataResponse {
2718        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2719            $result = $this->projectService->deleteCategory($projectid, $categoryid);
2720            if (isset($result['success'])) {
2721                return new DataResponse($categoryid);
2722            } else {
2723                return new DataResponse($result, 400);
2724            }
2725        } else {
2726            return new DataResponse(
2727                ['message' => $this->trans->t('You are not allowed to manage categories')],
2728                403
2729            );
2730        }
2731    }
2732
2733    /**
2734     * @NoAdminRequired
2735     */
2736    public function addCurrency(string $projectid, string $name, float $rate): DataResponse {
2737        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2738            $result = $this->projectService->addCurrency($projectid, $name, $rate);
2739            if (is_numeric($result)) {
2740                return new DataResponse($result);
2741            } else {
2742                return new DataResponse($result, 400);
2743            }
2744        } else {
2745            return new DataResponse(
2746                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2747                403
2748            );
2749        }
2750    }
2751
2752    /**
2753     * @NoAdminRequired
2754     * @NoCSRFRequired
2755     * @PublicPage
2756     * @CORS
2757     */
2758    public function apiAddCurrency(string $projectid, string $password, string $name, float $rate): DataResponse {
2759        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2760        if (
2761            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2762            || ($publicShareInfo !== null
2763                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2764                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2765        ) {
2766            $result = $this->projectService->addCurrency($publicShareInfo['projectid'] ?? $projectid, $name, $rate);
2767            if (is_numeric($result)) {
2768                // inserted currency id
2769                return new DataResponse($result);
2770            } else {
2771                return new DataResponse($result, 400);
2772            }
2773        } else {
2774            return new DataResponse(
2775                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2776                401
2777            );
2778        }
2779    }
2780
2781    /**
2782     * @NoAdminRequired
2783     * @NoCSRFRequired
2784     * @CORS
2785     */
2786    public function apiPrivAddCurrency(string $projectid, string $name, float $rate): DataResponse {
2787        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2788            $result = $this->projectService->addCurrency($projectid, $name, $rate);
2789            if (is_numeric($result)) {
2790                // inserted bill id
2791                return new DataResponse($result);
2792            } else {
2793                return new DataResponse($result, 400);
2794            }
2795        } else {
2796            return new DataResponse(
2797                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2798                403
2799            );
2800        }
2801    }
2802
2803    /**
2804     * @NoAdminRequired
2805     */
2806    public function editCurrency(string $projectid, int $currencyid, string $name, float $rate): DataResponse {
2807        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2808            $result = $this->projectService->editCurrency($projectid, $currencyid, $name, $rate);
2809            if (!isset($result['message'])) {
2810                return new DataResponse($result);
2811            } else {
2812                return new DataResponse($result, 400);
2813            }
2814        } else {
2815            return new DataResponse(
2816                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2817                403
2818            );
2819        }
2820    }
2821
2822    /**
2823     * @NoAdminRequired
2824     * @NoCSRFRequired
2825     * @PublicPage
2826     * @CORS
2827     */
2828    public function apiEditCurrency(string $projectid, string $password, int $currencyid, string $name, float $rate): DataResponse {
2829        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2830        if (
2831            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2832            || ($publicShareInfo !== null
2833                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2834                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2835        ) {
2836            $result = $this->projectService->editCurrency(
2837                $publicShareInfo['projectid'] ?? $projectid, $currencyid, $name, $rate
2838            );
2839            if (!isset($result['message'])) {
2840                return new DataResponse($result);
2841            } else {
2842                return new DataResponse($result, 403);
2843            }
2844        } else {
2845            return new DataResponse(
2846                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2847                401
2848            );
2849        }
2850    }
2851
2852    /**
2853     * @NoAdminRequired
2854     * @NoCSRFRequired
2855     * @CORS
2856     */
2857    public function apiPrivEditCurrency(string $projectid, int $currencyid, string $name, float $rate): DataResponse {
2858        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2859            $result = $this->projectService->editCurrency($projectid, $currencyid, $name, $rate);
2860            if (!isset($result['message'])) {
2861                return new DataResponse($result);
2862            } else {
2863                return new DataResponse($result, 403);
2864            }
2865        } else {
2866            return new DataResponse(
2867                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2868                403
2869            );
2870        }
2871    }
2872
2873    /**
2874     * @NoAdminRequired
2875     */
2876    public function deleteCurrency(string $projectid, int $currencyid): DataResponse {
2877        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2878            $result = $this->projectService->deleteCurrency($projectid, $currencyid);
2879            if (isset($result['success'])) {
2880                return new DataResponse($currencyid);
2881            } else {
2882                return new DataResponse($result, 400);
2883            }
2884        } else {
2885            return new DataResponse(
2886                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2887                403
2888            );
2889        }
2890    }
2891
2892    /**
2893     * @NoAdminRequired
2894     * @NoCSRFRequired
2895     * @PublicPage
2896     * @CORS
2897     */
2898    public function apiDeleteCurrency(string $projectid, string $password, int $currencyid): DataResponse {
2899        $publicShareInfo = $this->projectService->getProjectInfoFromShareToken($projectid);
2900        if (
2901            ($this->checkLogin($projectid, $password) && $this->projectService->getGuestAccessLevel($projectid) >= Application::ACCESS_LEVELS['maintainer'])
2902            || ($publicShareInfo !== null
2903                && (is_null($publicShareInfo['password']) || $password === $publicShareInfo['password'])
2904                && $publicShareInfo['accesslevel'] >= Application::ACCESS_LEVELS['maintainer'])
2905        ) {
2906            $result = $this->projectService->deleteCurrency($publicShareInfo['projectid'] ?? $projectid, $currencyid);
2907            if (isset($result['success'])) {
2908                return new DataResponse($currencyid);
2909            } else {
2910                return new DataResponse($result, 400);
2911            }
2912        } else {
2913            return new DataResponse(
2914                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2915                401
2916            );
2917        }
2918    }
2919
2920    /**
2921     * @NoAdminRequired
2922     * @NoCSRFRequired
2923     * @CORS
2924     */
2925    public function apiPrivDeleteCurrency(string $projectid, int $currencyid): DataResponse {
2926        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['maintainer']) {
2927            $result = $this->projectService->deleteCurrency($projectid, $currencyid);
2928            if (isset($result['success'])) {
2929                return new DataResponse($currencyid);
2930            } else {
2931                return new DataResponse($result, 400);
2932            }
2933        } else {
2934            return new DataResponse(
2935                ['message' => $this->trans->t('You are not allowed to manage currencies')],
2936                403
2937            );
2938        }
2939    }
2940
2941    /**
2942     * @NoAdminRequired
2943     */
2944    public function addUserShare(string $projectid, string $userid, int $accesslevel = Application::ACCESS_LEVELS['participant'],
2945                                bool $manually_added = true): DataResponse {
2946        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
2947            $result = $this->projectService->addUserShare($projectid, $userid, $this->userId, $accesslevel, $manually_added);
2948            if (!isset($result['message'])) {
2949                return new DataResponse($result);
2950            } else {
2951                return new DataResponse($result, 400);
2952            }
2953        } else {
2954            return new DataResponse(
2955                ['message' => $this->trans->t('You are not allowed to edit this project')],
2956                403
2957            );
2958        }
2959    }
2960
2961    /**
2962     * @NoAdminRequired
2963     */
2964    public function deleteUserShare(string $projectid, int $shid): DataResponse {
2965        // allow to delete share if user perms are at least participant AND if this share perms are <= user perms
2966        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
2967        $shareAccessLevel = $this->projectService->getShareAccessLevel($projectid, $shid);
2968        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant'] && $userAccessLevel >= $shareAccessLevel) {
2969            $result = $this->projectService->deleteUserShare($projectid, $shid, $this->userId);
2970            if (isset($result['success'])) {
2971                return new DataResponse('OK');
2972            } else {
2973                return new DataResponse($result, 400);
2974            }
2975        } else {
2976            return new DataResponse(
2977                ['message' => $this->trans->t('You are not allowed to remove this shared access')],
2978                403
2979            );
2980        }
2981    }
2982
2983    /**
2984     * @NoAdminRequired
2985     */
2986    public function addPublicShare(string $projectid): DataResponse {
2987        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
2988            $result = $this->projectService->addPublicShare($projectid);
2989            if (is_array($result)) {
2990                return new DataResponse($result);
2991            } else {
2992                return new DataResponse($result, 400);
2993            }
2994        } else {
2995            return new DataResponse(
2996                ['message' => $this->trans->t('You are not allowed to add public shared accesses')],
2997                403
2998            );
2999        }
3000    }
3001
3002    /**
3003     * @NoAdminRequired
3004     */
3005    public function deletePublicShare(string $projectid, int $shid): DataResponse {
3006        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
3007        $shareAccessLevel = $this->projectService->getShareAccessLevel($projectid, $shid);
3008        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant'] && $userAccessLevel >= $shareAccessLevel) {
3009            $result = $this->projectService->deletePublicShare($projectid, $shid);
3010            if (isset($result['success'])) {
3011                return new DataResponse('OK');
3012            } else {
3013                return new DataResponse($result, 400);
3014            }
3015        } else {
3016            return new DataResponse(
3017                ['message' => $this->trans->t('You are not allowed to remove this shared access')],
3018                403
3019            );
3020        }
3021    }
3022
3023    /**
3024     * @NoAdminRequired
3025     */
3026    public function addGroupShare(string $projectid, string $groupid): DataResponse {
3027        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
3028            $result = $this->projectService->addGroupShare($projectid, $groupid, $this->userId);
3029            if (!isset($result['message'])) {
3030                return new DataResponse($result);
3031            } else {
3032                return new DataResponse($result, 400);
3033            }
3034        } else {
3035            return new DataResponse(
3036                ['message' => $this->trans->t('You are not allowed to edit this project')],
3037                403
3038            );
3039        }
3040    }
3041
3042    /**
3043     * @NoAdminRequired
3044     */
3045    public function deleteGroupShare(string $projectid, int $shid): DataResponse {
3046        // allow to delete share if user perms are at least participant AND if this share perms are <= user perms
3047        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
3048        $shareAccessLevel = $this->projectService->getShareAccessLevel($projectid, $shid);
3049        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant'] && $userAccessLevel >= $shareAccessLevel) {
3050            $result = $this->projectService->deleteGroupShare($projectid, $shid, $this->userId);
3051            if (isset($result['success'])) {
3052                return new DataResponse('OK');
3053            } else {
3054                return new DataResponse($result, 400);
3055            }
3056        } else {
3057            return new DataResponse(
3058                ['message' => $this->trans->t('You are not allowed to remove this shared access')],
3059                403
3060            );
3061        }
3062    }
3063
3064    /**
3065     * @NoAdminRequired
3066     */
3067    public function addCircleShare(string $projectid, string $circleid): DataResponse {
3068        if ($this->projectService->getUserMaxAccessLevel($this->userId, $projectid) >= Application::ACCESS_LEVELS['participant']) {
3069            $result = $this->projectService->addCircleShare($projectid, $circleid, $this->userId);
3070            if (!isset($result['message'])) {
3071                return new DataResponse($result);
3072            } else {
3073                return new DataResponse($result, 400);
3074            }
3075        } else {
3076            return new DataResponse(
3077                ['message' => $this->trans->t('You are not allowed to edit this project')],
3078                403
3079            );
3080        }
3081    }
3082
3083    /**
3084     * @NoAdminRequired
3085     */
3086    public function deleteCircleShare(string $projectid, int $shid): DataResponse {
3087        // allow to delete share if user perms are at least participant AND if this share perms are <= user perms
3088        $userAccessLevel = $this->projectService->getUserMaxAccessLevel($this->userId, $projectid);
3089        $shareAccessLevel = $this->projectService->getShareAccessLevel($projectid, $shid);
3090        if ($userAccessLevel >= Application::ACCESS_LEVELS['participant'] && $userAccessLevel >= $shareAccessLevel) {
3091            $result = $this->projectService->deleteCircleShare($projectid, $shid, $this->userId);
3092            if (isset($result['success'])) {
3093                return new DataResponse('OK');
3094            } else {
3095                return new DataResponse($result, 400);
3096            }
3097        } else {
3098            return new DataResponse(
3099                ['message' => $this->trans->t('You are not allowed to remove this shared access')],
3100                403
3101            );
3102        }
3103    }
3104
3105    /**
3106     * @NoAdminRequired
3107     */
3108    public function getPublicFileShare(string $path): DataResponse {
3109        $cleanPath = str_replace(array('../', '..\\'), '',  $path);
3110        $userFolder = $this->root->getUserFolder($this->userId);
3111        if ($userFolder->nodeExists($cleanPath)) {
3112            $file = $userFolder->get($cleanPath);
3113            if ($file->getType() === FileInfo::TYPE_FILE) {
3114                if ($file->isShareable()) {
3115                    $shares = $this->shareManager->getSharesBy($this->userId,
3116                        IShare::TYPE_LINK, $file, false, 1, 0);
3117                    if (count($shares) > 0) {
3118                        foreach($shares as $share) {
3119                            if ($share->getPassword() === null) {
3120                                $token = $share->getToken();
3121                                break;
3122                            }
3123                        }
3124                    } else {
3125                        $share = $this->shareManager->newShare();
3126                        $share->setNode($file);
3127                        $share->setPermissions(Constants::PERMISSION_READ);
3128                        $share->setShareType(IShare::TYPE_LINK);
3129                        $share->setSharedBy($this->userId);
3130                        $share = $this->shareManager->createShare($share);
3131                        $token = $share->getToken();
3132                    }
3133                    $response = new DataResponse(['token' => $token]);
3134                } else {
3135                    $response = new DataResponse(['message' => $this->trans->t('Access denied')], 403);
3136                }
3137            } else {
3138                $response = new DataResponse(['message' => $this->trans->t('Access denied')], 403);
3139            }
3140        } else {
3141            $response = new DataResponse(['message' => $this->trans->t('Access denied')], 403);
3142        }
3143        return $response;
3144    }
3145
3146    /**
3147     * @NoAdminRequired
3148     */
3149    public function exportCsvSettlement(string $projectid, ?int $centeredOn = null, ?int $maxTimestamp = null): DataResponse {
3150        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
3151            $result = $this->projectService->exportCsvSettlement($projectid, $this->userId, $centeredOn, $maxTimestamp);
3152            if (isset($result['path'])) {
3153                return new DataResponse($result);
3154            } else {
3155                return new DataResponse($result, 400);
3156            }
3157        } else {
3158            return new DataResponse(
3159                ['message' => $this->trans->t('You are not allowed to export this project settlement')],
3160                403
3161            );
3162        }
3163    }
3164
3165    /**
3166     * @NoAdminRequired
3167     */
3168    public function exportCsvStatistics(string $projectid, ?int $tsMin = null, ?int $tsMax = null,
3169                                        ?int $paymentModeId = null, ?int $category = null,
3170                                        ?float $amountMin = null, ?float $amountMax = null, int $showDisabled = 1,
3171                                        ?int $currencyId = null): DataResponse {
3172        if ($this->projectService->userCanAccessProject($this->userId, $projectid)) {
3173            $result = $this->projectService->exportCsvStatistics(
3174                $projectid, $this->userId, $tsMin, $tsMax,
3175                $paymentModeId, $category, $amountMin, $amountMax,
3176                $showDisabled !== 0, $currencyId
3177            );
3178            if (isset($result['path'])) {
3179                return new DataResponse($result);
3180            } else {
3181                return new DataResponse($result, 400);
3182            }
3183        } else {
3184            return new DataResponse(
3185                ['message' => $this->trans->t('You are not allowed to export this project statistics')],
3186                403
3187            );
3188        }
3189    }
3190
3191    /**
3192     * @NoAdminRequired
3193     */
3194    public function exportCsvProject(string $projectid, ?string $name = null, ?string $uid = null): DataResponse {
3195        $userId = $uid;
3196        if ($this->userId) {
3197            $userId = $this->userId;
3198        }
3199
3200        if ($this->projectService->userCanAccessProject($userId, $projectid)) {
3201            $result = $this->projectService->exportCsvProject($projectid, $userId, $name);
3202            if (isset($result['path'])) {
3203                return new DataResponse($result);
3204            } else {
3205                return new DataResponse($result, 400);
3206            }
3207        } else {
3208            return new DataResponse(
3209                ['message' => $this->trans->t('You are not allowed to export this project')],
3210                403
3211            );
3212        }
3213    }
3214
3215    /**
3216     * @NoAdminRequired
3217     */
3218    public function importCsvProject(string $path): DataResponse {
3219        $result = $this->projectService->importCsvProject($path, $this->userId);
3220        if (isset($result['project_id'])) {
3221            $projInfo = $this->projectService->getProjectInfo($result['project_id']);
3222            $projInfo['myaccesslevel'] = Application::ACCESS_LEVELS['admin'];
3223            return new DataResponse($projInfo);
3224        } else {
3225            return new DataResponse($result, 400);
3226        }
3227    }
3228
3229    /**
3230     * @NoAdminRequired
3231     */
3232    public function importSWProject(string $path): DataResponse {
3233        $result = $this->projectService->importSWProject($path, $this->userId);
3234        if (isset($result['project_id'])) {
3235            $projInfo = $this->projectService->getProjectInfo($result['project_id']);
3236            $projInfo['myaccesslevel'] = Application::ACCESS_LEVELS['admin'];
3237            return new DataResponse($projInfo);
3238        } else {
3239            return new DataResponse($result, 400);
3240        }
3241    }
3242
3243    /**
3244     * Used by MoneyBuster to check if weblogin is valid
3245     * @NoAdminRequired
3246     * @NoCSRFRequired
3247     */
3248    public function apiPing(): DataResponse {
3249        $response = new DataResponse([$this->userId]);
3250        $csp = new ContentSecurityPolicy();
3251        $csp->addAllowedImageDomain('*')
3252            ->addAllowedMediaDomain('*')
3253            ->addAllowedConnectDomain('*');
3254        $response->setContentSecurityPolicy($csp);
3255        return $response;
3256    }
3257
3258    /**
3259     * @NoAdminRequired
3260     */
3261    public function getBillActivity(?int $since): DataResponse {
3262        $result = $this->projectService->getBillActivity($this->userId, $since);
3263        if (isset($result['error'])) {
3264            return new DataResponse($result, 400);
3265        } else {
3266            return new DataResponse($result);
3267        }
3268    }
3269}