diff --git a/README.md b/README.md index 9aa72ae..6517833 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ LBCAlerte for YunoHost Logiciel pour être alerté de nouvelles annonces par mail, flux RSS et SMS sur Leboncoin et d'autres sites d'annonces. -**Version incluse:** 3.2 +**Version incluse:** 3.3 ## Limitations diff --git a/scripts/install b/scripts/install index 73dc086..721c919 100644 --- a/scripts/install +++ b/scripts/install @@ -27,8 +27,7 @@ sudo mkdir -p $src_path/var sudo cp -a ../sources/. $src_path # Set permissions to app files -sudo chown -R root: $src_path -sudo chown -R www-data: $src_path/var +sudo chown -R www-data: $src_path # Generate MySQL password and create database dbuser=$app diff --git a/scripts/upgrade b/scripts/upgrade new file mode 100644 index 0000000..c5b621d --- /dev/null +++ b/scripts/upgrade @@ -0,0 +1,45 @@ +#!/bin/bash + +# Exit on command errors and treat unset variables as an error +set -eu + +# See comments in install script +app=$YNH_APP_INSTANCE_NAME + +# Source YunoHost helpers +source /usr/share/yunohost/helpers + +# Retrieve app settings +domain=$(ynh_app_setting_get "$app" domain) +path=$(ynh_app_setting_get "$app" path) +admin=$(ynh_app_setting_get "$app" admin) +is_public=$(ynh_app_setting_get "$app" is_public) +language=$(ynh_app_setting_get "$app" language) + +# Remove trailing "/" for next commands +path=${path%/} + +# Copy source files +src_path=/var/www/$app +sudo mkdir -p $src_path +sudo cp -a ../sources/. $src_path + +# Set permissions to app files +# you may need to make some file and/or directory writeable by www-data (nginx user) +sudo chown -R www-data: $src_path + +# Modify Nginx configuration file and copy it to Nginx conf directory +nginx_conf=../conf/nginx.conf +sed -i "s@YNH_WWW_PATH@$path@g" $nginx_conf +sed -i "s@YNH_WWW_ALIAS@$src_path/@g" $nginx_conf + +sudo cp $nginx_conf /etc/nginx/conf.d/$domain.d/$app.conf + +# If app is public, add url to SSOWat conf as skipped_uris +if [[ $is_public -eq 1 ]]; then + # See install script + ynh_app_setting_set "$app" unprotected_uris "/" +fi + +# Reload nginx service +sudo service nginx reload diff --git a/sources/CHANGELOG.txt b/sources/CHANGELOG.txt index 434ecd1..7e85789 100644 --- a/sources/CHANGELOG.txt +++ b/sources/CHANGELOG.txt @@ -1,4 +1,22 @@ +## Version 3.3 + + * ajout: possibilité de sauvegarder les annonces Leboncoin. + * ajout: possiblité d'ajouter une note aux annonces sauvegardées. + * ajout: possiblité de pré-remplir les champs de création d'alerte et flux RSS. + * ajout: test de la disponibilité de mysqli et php-curl à l'installation. + * ajout: un début d'API est mis en place. + * ajout: notification vers Joaoapps / Join. + * ajout: notifications vers Slack. + * ajout: possibilité d'enregistrer une ou plusieurs adresses mails par défaut. + * amélioration: les erreurs sont mieux gérées. + * amélioration: la page des paramètres utilisateur est refondu. + * amélioration: optimisation de la gestion des systèmes d'alerte. + * correction: encodage des caractères en base de données invalide. + * correction: encodage des caractères du contenu Leboncoin invalide. + * correction: factorisation du code de la tâche cron. + * correction: la réinitialisation des alertes ne fonctionne pas sur une nouvelle installation. + ## Version 3.2 * correction: si CURLOPT_FOLLOWLOCATION désactivé, suivre manuellement les redirections. diff --git a/sources/api.php b/sources/api.php new file mode 100644 index 0000000..35d1a65 --- /dev/null +++ b/sources/api.php @@ -0,0 +1,60 @@ +get("storage", "type", "files"); +if ($storageType == "db") { + $userStorage = new \App\Storage\Db\User($dbConnection); +} else { + $userStorage = new \App\Storage\File\User(DOCUMENT_ROOT."/var/users.db"); +} + + +// Identification par clé API +$auth = new Auth\ApiKey($userStorage); +if (!$userAuthed = $auth->authenticate()) { + header("HTTP/1.0 401 Unauthorized"); + exit; +} + +// Si une action de modification de données est demandée, il faut que ce soit +// en POST. +if (in_array($action, array("create", "modify", "delete")) + && $_SERVER["REQUEST_METHOD"] != "POST") { + header("HTTP/1.0 400 Bad Request"); + exit; +} + +$init = DOCUMENT_ROOT."/app/".$module."/init.php"; +$script = DOCUMENT_ROOT."/app/api/".$module."/".$action.".php"; + +if (!is_file($script)) { + header("HTTP/1.0 400 Bad Request"); + exit; +} + +if (is_file($init)) { + require $init; +} +$data = require $script; + +if (empty($data)) { + $data = array(); +} +echo json_encode($data); diff --git a/sources/app/admin/scripts/storage.php b/sources/app/admin/scripts/storage.php index 7b25d09..9360a66 100644 --- a/sources/app/admin/scripts/storage.php +++ b/sources/app/admin/scripts/storage.php @@ -41,6 +41,8 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { $_POST["options"]["password"], $_POST["options"]["dbname"]); if ($dbConnection->connect_error) { $errors["host"] = "Connexion impossible à la base de données."; + } else { + $dbConnection->set_charset("utf8"); } } } diff --git a/sources/app/admin/views/layout.phtml b/sources/app/admin/views/layout.phtml index 45e39cd..459fd9c 100644 --- a/sources/app/admin/views/layout.phtml +++ b/sources/app/admin/views/layout.phtml @@ -5,7 +5,8 @@ - + + diff --git a/sources/app/annonce/init.php b/sources/app/annonce/init.php new file mode 100644 index 0000000..a5dd526 --- /dev/null +++ b/sources/app/annonce/init.php @@ -0,0 +1,9 @@ +get("storage", "type", "files"); +if ($storageType == "db") { + $storage = new \App\Storage\Db\Ad($dbConnection, $userAuthed); +} else { + $storage = new \App\Storage\File\Ad(DOCUMENT_ROOT."/var/configs/backup-ads-".$auth->getUsername().".csv"); +} + +$adPhoto = new App\Storage\AdPhoto($userAuthed); diff --git a/sources/app/annonce/scripts/backup.php b/sources/app/annonce/scripts/backup.php new file mode 100644 index 0000000..98e0b1c --- /dev/null +++ b/sources/app/annonce/scripts/backup.php @@ -0,0 +1,40 @@ +request($url); + +try { + $parser = \AdService\ParserFactory::factory($url); +} catch (\AdService\Exception $e) { + $logger->err($e->getMessage()); +} + +$ad = $parser->processAd( + $content, + parse_url($url, PHP_URL_SCHEME) +); + +$ad_stored = $storage->fetchById($ad->getId()); +if ($ad_stored) { + if ($_SERVER["REQUEST_METHOD"] != "POST") { + return; + } + + // Supprime les photos + $adPhoto->delete($ad); +} + + +if (!$ad_stored) { + $ad_stored = new \App\Ad\Ad(); +} + +$ad_stored->setFromArray($ad->toArray()); +$storage->save($ad_stored); + +$adPhoto->import($ad_stored); + +header("LOCATION: ./?mod=annonce&a=view&id=".$ad->getId()); exit; \ No newline at end of file diff --git a/sources/app/annonce/scripts/form-comment.php b/sources/app/annonce/scripts/form-comment.php new file mode 100644 index 0000000..c90e9fe --- /dev/null +++ b/sources/app/annonce/scripts/form-comment.php @@ -0,0 +1,27 @@ +fetchById($_GET["id"]); +if (!$ad) { + header("LOCATION: ./?mod=annonce"); exit; +} + +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $comment = isset($_POST["comment"]) ? trim($_POST["comment"]) : ""; + $ad->setComment($comment); + $storage->save($ad); + + header("LOCATION: ./?mod=annonce&a=view&id=".$ad->getId()."&update=1"); + exit; +} + +try { + $ad_config = SiteConfigFactory::factory($ad->getLink()); +} catch (Exception $e) { + +} \ No newline at end of file diff --git a/sources/app/annonce/scripts/form-delete.php b/sources/app/annonce/scripts/form-delete.php new file mode 100644 index 0000000..c9676a0 --- /dev/null +++ b/sources/app/annonce/scripts/form-delete.php @@ -0,0 +1,17 @@ +fetchById($_GET["id"]); +if (!$ad) { + header("LOCATION: ./?mod=annonce"); exit; +} +if ($_SERVER["REQUEST_METHOD"] == "POST") { + if (isset($_POST["id"]) && $_POST["id"] == $_GET["id"]) { + $storage->delete($ad); + $adPhoto->delete($ad); + } + header("LOCATION: ./?mod=annonce"); exit; +} + +$referer = isset($_GET["r"]) ? $_GET["r"] : ""; \ No newline at end of file diff --git a/sources/app/annonce/scripts/form.php b/sources/app/annonce/scripts/form.php new file mode 100644 index 0000000..58e0aea --- /dev/null +++ b/sources/app/annonce/scripts/form.php @@ -0,0 +1,46 @@ +processAd( + $client->request($link), + parse_url($link, PHP_URL_SCHEME) + ); + if (!$ad) { + $errors["link"] = "Impossible de sauvegarder l'annonce (annonce hors ligne ou format des données invalides)."; + } + } + if (empty($errors) && !empty($ad)) { + $ad = $parser->processAd( + $client->request($link), + parse_url($link, PHP_URL_SCHEME) + ); + + $ad_stored = $storage->fetchById($ad->getId()); + if (!$ad_stored) { + $ad_stored = new \App\Ad\Ad(); + } + + $ad_stored->setFromArray($ad->toArray()); + $storage->save($ad_stored); + + $adPhoto->import($ad_stored); + + header("LOCATION: ./?mod=annonce"); + exit; + } +} \ No newline at end of file diff --git a/sources/app/annonce/scripts/index.php b/sources/app/annonce/scripts/index.php new file mode 100644 index 0000000..cffb677 --- /dev/null +++ b/sources/app/annonce/scripts/index.php @@ -0,0 +1,28 @@ +fetchAll($sort." ".$order); + + diff --git a/sources/app/annonce/scripts/view.php b/sources/app/annonce/scripts/view.php new file mode 100644 index 0000000..a858bf5 --- /dev/null +++ b/sources/app/annonce/scripts/view.php @@ -0,0 +1,18 @@ +fetchById($_GET["id"]); +if (!$ad) { + header("LOCATION: ./?mod=annonce"); exit; +} + +try { + $ad_config = SiteConfigFactory::factory($ad->getLink()); +} catch (Exception $e) { + +} \ No newline at end of file diff --git a/sources/app/annonce/views/backup.phtml b/sources/app/annonce/views/backup.phtml new file mode 100644 index 0000000..ee46816 --- /dev/null +++ b/sources/app/annonce/views/backup.phtml @@ -0,0 +1,20 @@ +
+
+ Annonce déjà sauvegardée + +

+ Vous avez déjà sauvegardé cette annonce. + Souhaitez vous écraser la sauvegarde ? +

+ + +

+ + + | Non +

+
+
diff --git a/sources/app/annonce/views/form-comment.phtml b/sources/app/annonce/views/form-comment.phtml new file mode 100644 index 0000000..3592cc5 --- /dev/null +++ b/sources/app/annonce/views/form-comment.phtml @@ -0,0 +1,17 @@ +

+ getTitle()); ?> +

+ +
+

+

+ +
+

+ + | annuler +

+

+
\ No newline at end of file diff --git a/sources/app/annonce/views/form-delete.phtml b/sources/app/annonce/views/form-delete.phtml new file mode 100644 index 0000000..fa4fb82 --- /dev/null +++ b/sources/app/annonce/views/form-delete.phtml @@ -0,0 +1,15 @@ +
+
+ Supprimer cette annonce ? + +

+ " /> + + | getId() : ''; ?>">Non +

+
+
diff --git a/sources/app/annonce/views/form.phtml b/sources/app/annonce/views/form.phtml new file mode 100644 index 0000000..ef6a605 --- /dev/null +++ b/sources/app/annonce/views/form.phtml @@ -0,0 +1,28 @@ +
+

Sauvegarder une annonce

+
+
+ +
+
+ + +

+ +
+
+

+ | annuler

+
+ + + + + + + + + + diff --git a/sources/app/annonce/views/index.phtml b/sources/app/annonce/views/index.phtml new file mode 100644 index 0000000..014582e --- /dev/null +++ b/sources/app/annonce/views/index.phtml @@ -0,0 +1,103 @@ +

+ + 1?"annonces sauvegardées":"annonce sauvegardée"; ?> | + + Sauvegarder une annonce +

+ + + + + + + + + + + + + + + + + + + + + + + getDate()))); + ?> + + + + + + + + + + + + + + + + +
+ +   + + Titre + + +   + + Auteur + + +   + + Date de sauvegarde + + +   + + Date de publication + + +   + + Catégorie + + +   + + Prix + + +   + + Code postal + + +   + + Villes +  
Aucune annonce sauvegardée
+ getPhotos()) : ?> + " alt="" + style="max-width: 100px; max-height: 100px;" /> + + getTitle()?htmlspecialchars($ad->getTitle()):"-"; ?>getAuthor()); ?>getDateCreated())); ?>getCategory()); ?> + getPrice()) : ?> + getCurrency()); ?> + + - + + getZipCode()); ?>getCity()); ?>
diff --git a/sources/app/annonce/views/view.phtml b/sources/app/annonce/views/view.phtml new file mode 100644 index 0000000..f24f285 --- /dev/null +++ b/sources/app/annonce/views/view.phtml @@ -0,0 +1,95 @@ +getDate()))); +?> +

+ < retour à la liste des annonces sauvegardées | + supprimer l'annonce +

+ +

+ getTitle()); ?> + "> +

+
+
+ +
+ Description +

getDescription())); ?>

+
+
+ Note +
+

getComment())); ?>

+

+ + Ajouter une note + + Modifier la note + +

+
+
+
+ getPhotos(); ?> +
+ + + +
+
diff --git a/sources/app/api/annonce/create.php b/sources/app/api/annonce/create.php new file mode 100644 index 0000000..d1a5bb4 --- /dev/null +++ b/sources/app/api/annonce/create.php @@ -0,0 +1,44 @@ + $_POST, + "errors" => array( + "link" => "Ce champ est obligatoire." + ) + ); +} + +$link = $_POST["link"]; +try { + $siteConfig = \AdService\SiteConfigFactory::factory($link); + $parser = \AdService\ParserFactory::factory($link); +} catch (\Exception $e) { + return array( + "data" => $_POST, + "errors" => array( + "link" => "Cette adresse ne semble pas valide." + ) + ); +} + +$ad = $parser->processAd( + $client->request($link), + parse_url($link, PHP_URL_SCHEME) +); + +$ad_stored = $storage->fetchById($ad->getId()); +if (!$ad_stored) { + $ad_stored = new \App\Ad\Ad(); +} + +$ad_stored->setFromArray($ad->toArray()); +$storage->save($ad_stored); + +$adPhoto = new App\Storage\AdPhoto($userAuthed); +$adPhoto->import($ad_stored); + +return $ad_stored->toArray(); \ No newline at end of file diff --git a/sources/app/api/annonce/delete.php b/sources/app/api/annonce/delete.php new file mode 100644 index 0000000..0e97cc6 --- /dev/null +++ b/sources/app/api/annonce/delete.php @@ -0,0 +1,16 @@ + $_POST, + "errors" => array( + "id" => "Un ID doit être fourni" + ) + ); +} + +$ad = $storage->fetchById($_POST["id"]); +if ($ad) { + $storage->delete($ad); + $adPhoto = new App\Storage\AdPhoto($userAuthed); + $adPhoto->delete($ad); +} diff --git a/sources/app/api/annonce/index.php b/sources/app/api/annonce/index.php new file mode 100644 index 0000000..2575eec --- /dev/null +++ b/sources/app/api/annonce/index.php @@ -0,0 +1,24 @@ +fetchAll($order_by); + +$baseurl = $config->get("general", "baseurl", ""); +$adPhoto = new App\Storage\AdPhoto($userAuthed); +$return = array(); +foreach ($ads AS $ad) { + $params = $ad->toArray(); + foreach ($params["photos"] AS $i => $photo) { + $params["photos"][$i]["local"] = $baseurl.$adPhoto->getPublicDestination($photo["local"]); + } + $return[$ad->getId()] = $params; +} + +return $return; \ No newline at end of file diff --git a/sources/app/data/notifications.php b/sources/app/data/notifications.php new file mode 100644 index 0000000..1faa32d --- /dev/null +++ b/sources/app/data/notifications.php @@ -0,0 +1,75 @@ +getNotificationsEnabled(); + $notifications_enabled["mail"] = array(); +} + +$data_notifications = array( + "mail" => array( + "list_label" => "Envoyer par email", + "form_label" => "par email", + "form_name" => "send_mail", + "enabled" => true, + ), + "freemobile" => array( + "label" => "SMS - Free Mobile", + "link" => "https://mobile.free.fr/moncompte/", + "list_label" => "SMS Free Mobile", + "form_label" => "par SMS Free Mobile", + "form_name" => "send_sms_free_mobile", + "enabled" => isset($notifications_enabled["freeMobile"]), + ), + "ovh" => array( + "label" => "SMS - OVH Telecom", + "cost" => "À partir de 0,07 € / SMS", + "link" => "https://www.ovhtelecom.fr/sms/", + "list_label" => "SMS OVH", + "form_label" => "par SMS OVH", + "form_name" => "send_sms_ovh", + "enabled" => isset($notifications_enabled["ovh"]), + ), + "pushbullet" => array( + "label" => "Pushbullet", + "link" => "https://www.pushbullet.com/", + "list_label" => "Pushbullet", + "form_label" => "par Pushbullet", + "form_name" => "send_pushbullet", + "enabled" => isset($notifications_enabled["pushbullet"]), + ), + "notifymyandroid" => array( + "label" => "NotifyMyAndroid", + "cost" => "5 notifications / jour (illimité en premium)", + "link" => "http://www.notifymyandroid.com/", + "list_label" => "NotityMyAndroid", + "form_label" => "par NotityMyAndroid", + "form_name" => "send_notifymyandroid", + "enabled" => isset($notifications_enabled["notifymyandroid"]), + ), + "pushover" => array( + "label" => "Pushover", + "link" => "https://pushover.net/", + "list_label" => "Pushover", + "form_label" => "par Pushover", + "form_name" => "send_pushover", + "enabled" => isset($notifications_enabled["pushover"]), + ), + "joaoappsjoin" => array( + "label" => "Joaoapps / Join", + "link" => "https://joaoapps.com/join/", + "list_label" => "Joaoapps / Join", + "form_label" => "par Joaoapps / Join", + "form_name" => "send_joaoappsjoin", + "enabled" => isset($notifications_enabled["joaoappsjoin"]), + ), + "slack" => array( + "label" => "Slack", + "cost" => "Ofre gratuite et premium", + "link" => "https://slack.com", + "list_label" => "Slack", + "form_label" => "par Slack", + "form_name" => "send_slack", + "enabled" => isset($notifications_enabled["slack"]), + ), +); diff --git a/sources/app/default/scripts/login.php b/sources/app/default/scripts/login.php index 93be68d..2421ca3 100644 --- a/sources/app/default/scripts/login.php +++ b/sources/app/default/scripts/login.php @@ -12,7 +12,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") { $auth->setUsername($username) ->setPassword(sha1($password)); if ($auth->authenticate()) { - if ($module == "default" && $action == "login") { + if (isset($_GET["a"]) && $_GET["a"] == "login") { $redirect = "./"; } else { $redirect = $_SERVER["REQUEST_URI"]; diff --git a/sources/app/default/views/layout.phtml b/sources/app/default/views/layout.phtml index 9b55c1f..089b421 100644 --- a/sources/app/default/views/layout.phtml +++ b/sources/app/default/views/layout.phtml @@ -5,7 +5,8 @@ - + + @@ -13,8 +14,9 @@

Système d'alerte Leboncoin