Location Editing Part 2

Bulk uploading has been added and some inconsistencies in the templates
have been addressed. Still needs work but it's starting to feel like a
cohesive experience.

With the base data entry funcationality in place, now really polishing
can begin as well as establishing what roles can do what.

Smoothing out entry editing will be addressed as well.
symfony-version
Ro 2 years ago
parent 3410abd70a
commit 26f3cbe994

@ -10,6 +10,7 @@
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.13",
"league/csv": "^9.0",
"rbdwllr/reallysimplejwt": "^5.0",
"sensio/framework-extra-bundle": "^6.2",
"symfony/console": "6.1.*",

86
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "eb3c50bec813d049150ad9f4cf2b9617",
"content-hash": "f31b264b29ff1c91409f2abfcd475ad0",
"packages": [
{
"name": "doctrine/annotations",
@ -1524,6 +1524,90 @@
],
"time": "2022-11-21T01:32:31+00:00"
},
{
"name": "league/csv",
"version": "9.8.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
"reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/9d2e0265c5d90f5dd601bc65ff717e05cec19b47",
"reference": "9d2e0265c5d90f5dd601bc65ff717e05cec19b47",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"php": "^7.4 || ^8.0"
},
"require-dev": {
"ext-curl": "*",
"ext-dom": "*",
"friendsofphp/php-cs-fixer": "^v3.4.0",
"phpstan/phpstan": "^1.3.0",
"phpstan/phpstan-phpunit": "^1.0.0",
"phpstan/phpstan-strict-rules": "^1.1.0",
"phpunit/phpunit": "^9.5.11"
},
"suggest": {
"ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes",
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"League\\Csv\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://github.com/nyamsprod/",
"role": "Developer"
}
],
"description": "CSV data manipulation made easy in PHP",
"homepage": "https://csv.thephpleague.com",
"keywords": [
"convert",
"csv",
"export",
"filter",
"import",
"read",
"transform",
"write"
],
"support": {
"docs": "https://csv.thephpleague.com",
"issues": "https://github.com/thephpleague/csv/issues",
"rss": "https://github.com/thephpleague/csv/releases.atom",
"source": "https://github.com/thephpleague/csv"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2022-01-04T00:13:07+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",

@ -50,5 +50,6 @@ sup {
color: var(--white);
padding: 2px;
border-radius: 3px;
vertical-align: text-bottom;
vertical-align: baseline;
font-family: var(--mono-type);
}

@ -18,5 +18,10 @@ section[role="loc-index"] {
section a {
color: var(--white);
border-bottom: 1px solid var(--highlight);
}
section a:hover {
border-bottom: 1px solid var(--secondary);
padding-bottom: 2px;
}

@ -12,6 +12,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RequestStack;
use Doctrine\Persistence\ManagerRegistry;
use App\Service\HandleLocations;
use Doctrine\DBAL\Connection;
//use App\Utils\PageRender;
//use App\Utils\StringTools;
use App\Service\Auth;
@ -36,6 +37,8 @@ class Locations extends AbstractController
RequestStack $requestStack,
Auth $auth,
HandleLocations $locations,
ManagerRegistry $doctrine,
Connection $connection,
string $pageNum
): Response {
$result = $auth->status();
@ -43,6 +46,11 @@ class Locations extends AbstractController
$session = $requestStack->getSession();
$member = $session->get("member");
$list = $locations->getLocationsPage($pageNum);
//$search = $connection->fetchAllAssociative("SELECT * FROM searchlocations('agenda')");
//var_dump($search[0]["name"]);
return $this->render("back/locations.twig", [
"title" => "Bad Space | Locations",
"handle" => $member->getHandle(),
@ -140,6 +148,86 @@ class Locations extends AbstractController
}
}
/**
* @Route("/den/locations/bulk-add", name="location-bulk-add")
*/
public function bulkAddLocation(
Request $request,
Auth $auth,
HandleLocations $locations,
ManagerRegistry $doctrine,
FileUploader $uploader
): Response {
$result = $auth->status();
if ($result["status"]) {
if ($request->getMethod() == "GET") {
return $this->render("back/locations.twig", [
"title" => "Bad Space | Locations | Bulk Add",
"mode" => "bulk-add"
]);
} else {
// do posting stuff
$token = $request->get("token");
$entityManager = $doctrine->getManager();
$notice = '';
if (!$this->isCsrfTokenValid("upload", $token)) {
$logger->info("CSRF failure");
return new Response(
"Operation not allowed",
Response::HTTP_BAD_REQUEST,
[
"content-type" => "text/plain",
]
);
}
//get file from post
$file = $request->files->get("myfile");
//grab extension
if (!empty($file)) {
$extention = substr(strrchr($file->getClientOriginalName(), "."), 1);
}
//check it out to make sure it's cool
if (
empty($file) ||
$extention != "csv"
) {
if (empty($file)) {
$notice = 'You didn\'t select a file, boss';
} elseif ($extention != "csv") {
$notice = "Only files of type .csv are accepted, slick. " . $extention;
}
return $this->render("back/locations.twig", [
"title" => "Bad Space | Locations | Add",
"notice" => $notice,
"mode" => "bulk-add"
]);
}
//if it's cool, send it to be processed
$response = $locations->addMultipleLocations($file, $result["id"]);
if ($response["status"]) {
$notice = "New locations added! Take a break.";
return $this->render("back/locations.twig", [
"title" => "Bad Space | Locations | Add",
"notice" => $response["message"],
"mode" => "bulk-add"
]);
} else {
return $this->render("back/locations.twig", [
"title" => "Bad Space | Locations | Add",
"notice" => $response["message"],
"mode" => "bulk-add"
]);
}
}
} else {
header("Location:/den");
return new Response("<html><body>LOGGED IN</body></html>");
}
}
/**
* @Route("/den/locations/edit/{uuid}", name="location-edit")
*/

@ -12,6 +12,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Uid\Uuid;
use App\Entity\Location;
use League\Csv\Reader;
//use App\Utils\StringTools;
@ -139,4 +140,97 @@ class HandleLocations
return $response = ["status" => false, "message" => $errorMessage];
}
}
/**
* Add new location to db
*
* @param Request $file object containing posted data
* @return Object
*/
public function addMultipleLocations($file, $memberId)
{
//read csv
$csv = Reader::createFromPath($file, "r");
$csv->setHeaderOffset(0);
$records = $csv->getRecords();
$recordCount = count($csv);
$duplicates = 0;
$errorMessage = null;
// Save image
//extract data row by row
foreach ($records as $offset => $row) {
$name = $row["Name"];
$url = $row["Url"];
$images = $row["Images"];
$desc = $row["Description"];
$tags = $row["Tags"];
$ratings = $row["Rating"];
$imgs = explode(',', $images);
$examples = [];
//check to see if location already exists
$list = $this->entityManager->getRepository(Location::class);
$entry = $list->findOneBy(["name" => $name]);
if ($entry) {
++$duplicates;
} else {
$errorMessage = null;
$location = new Location();
$location->setName($name);
$location->setUrl($url);
$location->setDescription($desc);
$location->setTags($tags);
$location->setRating($ratings);
//grab images, move them to dir and set image array
foreach ($imgs as $img) {
$imageName = uniqid() . ".jpg";
$path = "../public/assets/images/examples/" . $imageName;
array_push($examples, $imageName);
file_put_contents($path, file_get_contents(trim($img)));
}
$location->setImages($examples);
//set defaults
$location->setUuid(Uuid::v4());
$location->setActive(false);
$location->setCreatedAt(new \DateTimeImmutable());
$location->setUpdatedAt(new \DateTimeImmutable());
$location->setAddedBy($memberId);
$this->entityManager->persist($location);
}
}
try {
$this->entityManager->flush();
} catch (PDOException $error) {
$errorMessage = $error->getMessage();
} catch (DBALException $error) {
$errorMessage = $error->getMessage();
} catch (ORMException $error) {
$errorMessage = $error->getMessage();
} catch (Exception $error) {
$errorMessage = $error->getMessage();
} catch (SyntaxErrorException $e) {
$errorMessage = $error->getMessage();
}
if ($duplicates > 0) {
$message = $duplicates . " of " . $recordCount . " location entries were duplicates";
} else {
$message = "Locations Added. Nice Job!";
}
// return result status
if ($errorMessage == null) {
return $response = [
"status" => true,
"message" => $message,
];
} else {
return $response = ["status" => false, "message" => $errorMessage];
}
}
}

@ -17,15 +17,24 @@
{% if mode == "add" %}
<h2>Add New Location</h2>
{{ include("forms/add-location.twig") }}
{% elseif mode =='bulk-add' %}
<h2>Add Multiple Locations</h2>
{{ include("forms/bulk-add-location.twig") }}
{% elseif mode == "edit" %}
<h2>Editing
{{ location.name }}</h2>
{{ include("forms/edit-location.twig") }}
{% else %}
<h2>Take care. These are bad places.</h2>
<a href="/den/locations/add">Add Location</a>
|
<a href="/den/locations/bulk-add">Add Multiple Locations</a>
<br>
<h3>Bad Spaces</h3>
{% for location in list.locations %}
<a href="/den/locations/edit/{{ location.uuid }}">
<sup>ID:{{ location.id }}</sup>
<a href="/den/locations/edit/{{ location.uuid }}">
{{ location.name }}</a><br/>
{% endfor %}
{% endif %}

@ -0,0 +1,8 @@
<form action="{{ path('location-bulk-add') }}" method="post" enctype="multipart/form-data">
<label for="myfile">Upload CSV</label>
<br>
<input type="file" name="myfile" id="myfile"></div>
<br/>
<input type="hidden" name="token" value="{{ csrf_token('upload') }}"/>
<button type="submit">Upload Locations</button>
</form>

@ -21,7 +21,16 @@
<option value="silence">Silence</option>
<option value="defederate" selected>Defederate</option>
{% endif %}
</select><br/>
<label>Include in search results</label><br/>
<select name="active">
{% if location.active %}
<option value="false">No</option>
<option value="true" selected>Yes</option>
{% else %}
<option value="false" selected>No</option>
<option value="true">Yes</option>
{% endif %}
</select>
<br/>
<label>Images</label><br/>

Loading…
Cancel
Save