Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
- name: CS
run: |
cd galette-core/galette/plugins/plugin-maps
../../vendor/bin/phpcs lib/ ./*.php
../../vendor/bin/phpcs lib/ tests/ ./*.php

- name: CS Fixer
if: matrix.php-is-min
Expand Down
46 changes: 39 additions & 7 deletions lib/GaletteMaps/Controllers/MapsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ private function getMemberDeps(): array
];
}

/**
* Message for a member that does not exist
*
* @param int $id Requested member ID
*/
private function getNoMemberMessage(int $id): string
{
return sprintf(
//TRANS: parameter is the member identifier
_T('No member #%1$s.'),
$id
);
}

/**
* Main route
*
Expand Down Expand Up @@ -91,10 +105,22 @@ public function map(Request $request, Response $response): Response
*/
public function localizeMember(Request $request, Response $response, ?int $id = null): Response
{
if ($id === null) {
$id = (int)$this->login->id;
if ($id === null && $this->login->isSuperAdmin()) {
return $this->redirectWithErrors(
response: $response,
errors: [_T('Superadmin cannot be localized.', 'maps')],
redirect_url: $this->routeparser->urlFor('slash')
);
}
$id ??= (int)$this->login->id;
$member = new Adherent($this->zdb, $id, $this->getMemberDeps());
if ($member->id === null) {
return $this->redirectWithErrors(
response: $response,
errors: [$this->getNoMemberMessage($id)],
redirect_url: $this->routeparser->urlFor('slash')
);
}

if (!$member->canShow($this->login)) {
Analog::log(
Expand Down Expand Up @@ -229,18 +255,21 @@ public function storePreferences(Request $request, Response $response): Response
}
}

$stored = true;
//each call resets errors of the previous one
$errors = [];
foreach ($values as $name => $value) {
$stored = $this->preferences->setValue($name, $value, $this->login) && $stored;
if (!$this->preferences->setValue($name, $value, $this->login)) {
$errors = array_merge($errors, $this->preferences->getErrors());
}
}

if ($stored) {
if (count($errors) === 0) {
$this->flash->addMessage(
'success_detected',
_T('Maps settings have been saved.', 'maps')
);
} else {
foreach ($this->preferences->getErrors() as $error) {
foreach ($errors as $error) {
$this->flash->addMessage('error_detected', $error);
}
}
Expand Down Expand Up @@ -273,7 +302,10 @@ public function ILiveHere(Request $request, Response $response, ?int $id = null)
} else {
$id ??= (int)$this->login->id;
$member = new Adherent($this->zdb, $id, $this->getMemberDeps());
if (!$member->canEdit($this->login)) {
if ($member->id === null) {
$error = $this->getNoMemberMessage($id);
$status = 404;
} elseif (!$member->canEdit($this->login)) {
Analog::log(
'Logged in member ' . $this->login->login
. ' has tried to change coordinates of member #' . $id
Expand Down
3 changes: 2 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
parameters:
parallel:
maximumNumberOfProcesses: 2
level: 3
level: 6
paths:
- lib/
- tests/
scanFiles:
- _config.inc.php
- ../../includes/sys_config/paths.inc.php
Expand Down
180 changes: 180 additions & 0 deletions tests/GaletteMaps/Controllers/tests/units/MapsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Galette\Entity\Adherent;
use Galette\Tests\GaletteRoutingTestCase;
use GaletteMaps\Coordinates;
use GaletteMaps\TileProviders;

/**
* Maps controller tests
Expand All @@ -32,6 +33,7 @@ public function tearDown(): void
{
$this->login->logout();
$this->preferences->pref_bool_groupsmanagers_edit_member = false;
$this->preferences->pref_bool_publicpages = true;
$this->zdb->execute($this->zdb->delete(MAPS_PREFIX . Coordinates::TABLE));
parent::tearDown();
}
Expand Down Expand Up @@ -305,6 +307,184 @@ public function testMapEscapesNames(): void
$this->assertStringContainsString('\u0026lt\u003Bimg\u0020src', $body);
}

/**
* Map is displayed to visitors only when public pages allow it
*/
public function testPublicMap(): void
{
$member_one = $this->getMemberOne();
$this->assertTrue((new Coordinates())->setCoords($member_one->id, 48.85, 2.35));
$request = $this->createRequest('maps_map');

$this->preferences->pref_bool_publicpages = false;
$test_response = $this->app->handle($request);
$this->assertSame(
['Location' => [$this->routeparser->urlFor('slash')]],
$test_response->getHeaders()
);
$this->assertSame(302, $test_response->getStatusCode());
$this->expectFlashData(['error_detected' => ['Unauthorized']]);

$this->preferences->pref_bool_publicpages = true;
$this->preferences->pref_publicpages_visibility_generic = \Galette\Core\Preferences::PUBLIC_PAGES_VISIBILITY_PUBLIC;
$test_response = $this->app->handle($request);
$this->assertSame(200, $test_response->getStatusCode());
$body = (string)$test_response->getBody();
$this->assertStringContainsString('_mapsBinded', $body);
//member one does not display its information
$this->assertStringNotContainsString('48.850000', $body);
}

/**
* Own localization page, with and without coordinates
*/
public function testOwnPage(): void
{
$member_one = $this->getMemberOne();
$this->logMember($this->dataAdherentOne());
$request = $this->createRequest('maps_mymap');

//no town search without a town
$update = $this->zdb->update(Adherent::TABLE);
$update->set(['ville_adh' => ''])->where([Adherent::PK => $member_one->id]);
$this->zdb->execute($update);
$test_response = $this->app->handle($request);
$this->assertSame(200, $test_response->getStatusCode());
$body = (string)$test_response->getBody();
$this->assertStringNotContainsString('id="possible_towns"', $body);
$this->assertStringNotContainsString('id="removecoords"', $body);
$this->assertStringContainsString('onMapClick', $body);

$this->assertTrue((new Coordinates())->setCoords($member_one->id, 48.85, 2.35));
$test_response = $this->app->handle($request);
$this->assertSame(200, $test_response->getStatusCode());
$body = (string)$test_response->getBody();
$this->assertStringContainsString('var _lat = 48.850000;', $body);
$this->assertStringContainsString('id="removecoords"', $body);
$this->assertStringContainsString('I\\u0020live\\u0020here\\u0021', $body);
}

/**
* Only administrators reach preferences
*/
public function testPreferencesAccess(): void
{
$this->getMemberOne();
$this->logMember($this->dataAdherentOne());
$this->expectAuthMiddlewareRefused($this->app->handle($this->createRequest('maps_preferences')));
$this->login->logout();

$this->logSuperAdmin();
$test_response = $this->app->handle($this->createRequest('maps_preferences'));
$this->assertSame(200, $test_response->getStatusCode());
$body = (string)$test_response->getBody();
$this->assertStringContainsString('id="pref_maps_tiles_provider"', $body);
$this->assertStringContainsString('OpenFreeMap, light grey', $body);
}

/**
* Store preferences
*/
public function testStorePreferences(): void
{
$this->logSuperAdmin();
$store = function (array $data): \Psr\Http\Message\ResponseInterface {
$request = $this->createRequest('maps_store_preferences', [], 'POST')->withParsedBody($data);
$test_response = $this->app->handle($request);
$this->assertSame(
['Location' => [$this->routeparser->urlFor('maps_preferences')]],
$test_response->getHeaders()
);
$this->assertSame(302, $test_response->getStatusCode());
return $test_response;
};

try {
$store([TileProviders::PREF_PROVIDER => 'osm']);
$this->expectFlashData(['success_detected' => ['Maps settings have been saved.']]);
$this->preferences->load();
$this->assertSame('osm', TileProviders::resolve($this->preferences)['id']);

//own values need an address
$store([TileProviders::PREF_PROVIDER => TileProviders::CUSTOM, TileProviders::PREF_URL => ' ']);
$this->expectFlashData(['error_detected' => ['An address is required to use your own background map.']]);
$this->preferences->load();
$this->assertSame('osm', TileProviders::resolve($this->preferences)['id']);

//unticked vector box is not posted
$store([
TileProviders::PREF_PROVIDER => TileProviders::CUSTOM,
TileProviders::PREF_URL => 'https://tiles.example.org/{z}/{x}/{y}.png',
TileProviders::PREF_ATTRIBUTION => 'Example',
TileProviders::PREF_MAXZOOM => '17',
TileProviders::PREF_SUBDOMAINS => '',
]);
$this->expectFlashData(['success_detected' => ['Maps settings have been saved.']]);
$this->preferences->load();
$tiles = TileProviders::resolve($this->preferences);
$this->assertSame(TileProviders::CUSTOM, $tiles['id']);
$this->assertFalse($tiles['vector']);
$this->assertSame('https://tiles.example.org/{z}/{x}/{y}.png', $tiles['url']);
$this->assertSame(17, $tiles['maxzoom']);

//out of range zoom is refused
$store([
TileProviders::PREF_PROVIDER => TileProviders::CUSTOM,
TileProviders::PREF_URL => 'https://tiles.example.org/{z}/{x}/{y}.png',
TileProviders::PREF_MAXZOOM => '30',
]);
//core has no range error message
$this->expectFlashData(['error_detected' => ['- Value for \'pref_maps_tiles_maxzoom\' must be a positive number!']]);
$this->preferences->load();
$this->assertSame(17, TileProviders::resolve($this->preferences)['maxzoom']);
} finally {
foreach (TileProviders::getSchema() as $name => $schema) {
$this->preferences->setValue($name, $schema['default'], $this->login);
}
}
}

/**
* Superadmin has no own localization page
*/
public function testSuperAdminOwnPage(): void
{
$this->logSuperAdmin();
$test_response = $this->app->handle($this->createRequest('maps_mymap'));
$this->assertSame(
['Location' => [$this->routeparser->urlFor('slash')]],
$test_response->getHeaders()
);
$this->assertSame(301, $test_response->getStatusCode());
$this->expectFlashData(['error_detected' => ['Superadmin cannot be localized.']]);
}

/**
* Localization of a member that does not exist
*/
public function testMissingMember(): void
{
$this->logSuperAdmin();
$test_response = $this->app->handle($this->createRequest('maps_localize_member', ['id' => '999999']));
$this->assertSame(
['Location' => [$this->routeparser->urlFor('slash')]],
$test_response->getHeaders()
);
$this->assertSame(301, $test_response->getStatusCode());
$this->expectFlashData(['error_detected' => ['No member #999999.']]);
//logged by Adherent on load
$this->expectLogEntry(Analog::ERROR, 'No member #999999');

$test_response = $this->postCoords(999999);
$this->assertSame(404, $test_response->getStatusCode());
$this->assertSame(
['res' => false, 'message' => 'No member #999999.'],
json_decode((string)$test_response->getBody(), true)
);
$this->expectLogEntry(Analog::ERROR, 'No member #999999');
$this->expectNoLogEntry();
}

/**
* Superadmin changes coordinates of any member, but has none
*/
Expand Down
5 changes: 3 additions & 2 deletions tests/GaletteMaps/tests/units/NominatimTowns.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace GaletteMaps\tests\units;

use Analog\Analog;
use Galette\Tests\GaletteTestCase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
Expand Down Expand Up @@ -61,8 +62,8 @@ public function testSearch(): void
],
$search->search('Valenciennes', 'France')
);
$this->expectLogEntry(\Analog::INFO, 'Town is already in list, ignore.');
$this->expectLogEntry(\Analog::INFO, 'Nominatim result "Rue de Valenciennes" is not a town');
$this->expectLogEntry(Analog::INFO, 'Town is already in list, ignore.');
$this->expectLogEntry(Analog::INFO, 'Nominatim result "Rue de Valenciennes" is not a town');

$this->assertCount(1, $this->requests);
/** @var Request $request */
Expand Down
Loading
Loading