Skip to content

Commit

Permalink
Merge branch 'timed-and-secured-assets-feature/issue-11/add-privacy-a…
Browse files Browse the repository at this point in the history
…nd-imprint-units' into develop

* timed-and-secured-assets-feature/issue-11/add-privacy-and-imprint-units:
  Rename patch
  feat: require privacy and imprint when run publicness > 0
  feat: add warning when setting privacy texts but no unit present
  feat: add confirmation on privacy unit removal
  refactor: add info texts for privacy settings
  fix: privacy text preview url
  refactor: add log messages; add info to set privacy texts when not set; add preview link for privacy texts;
  refactor: move privacy texts to run settings; add sql patch;
  feat: add tos and imprint to privacy unit; create pages for privacy pages; embed links to privacy pages in every survey pages footer;
  fix: return $output when privacy accepted
  feat: add privacy run unit
  Added Filetype check for uploading surveys
  fixed gitignore not ignoring database files
  removed credentials
  Added docker files
  • Loading branch information
rubenarslan committed Oct 1, 2024
2 parents 40358a6 + f35f738 commit 54b8eda
Show file tree
Hide file tree
Showing 14 changed files with 464 additions and 32 deletions.
64 changes: 43 additions & 21 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,59 @@

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [v0.21.5] - 01.10.2024
### Added
* compliance work
* added special user*facing static pages for privacy policy and terms of service
* added an option to require that a privacy policy exists before studies go public
* improved default footer text/imprint to include admin email address, links to privacy policy, ToS, settings

## [v0.21.4] - 10.07.2024
- implement JS changes for material design too
- bug fix for default session code regex
- default to exporting items when exporting run JSONs
- all newly created surveys have a default field "iteration" which is simply an auto-increment number from 1 to number of responses to survey
### Fixed
* bug fix for default session code regex
### Added
* implement JS changes for material design too
* default to exporting items when exporting run JSONs
* all newly created surveys have a default field "iteration" which is simply an auto-increment number from 1 to number of responses to survey

## [v0.21.3] - 21.06.2024
- autoset timezone for timezone inputs
- make user id/session code length flexible/configurable
- webshim number inputs to make the regional number formatting configurable
### Added
* autoset timezone for timezone inputs
* make user id/session code length flexible/configurable
* webshim number inputs to make the regional number formatting configurable

## [v0.21.2] - 02.06.2024
- bug fix (minify changed JS correctly)
### Fixed
* bug fix (minify changed JS correctly)

## [v0.21.1] - 01.06.2024
- simplify integration with labjs et al by
- not changing file names on upload
- allowing larger amounts of data to be stored in text fields
- allowing uploadable file types to be configurable
- add Reply-To option for email accounts
- allow default email accounts to be configured in settings.php, Reply-To defaults to admin email address
- allow superadmins to manually set admin account email addresses as verified
### Added
* simplify integration with labjs et al by
* not changing file names on upload
* allowing larger amounts of data to be stored in text fields
* allowing uploadable file types to be configurable
* add Reply-To option for email accounts
* allow default email accounts to be configured in settings.php, Reply-To defaults to admin email address
* allow superadmins to manually set admin account email addresses as verified


## [v0.21.0] - 07.03.2024
- make it easier to dockerise formr
- track bower_components to make it easier to collaborate on changes in CSS/JS
- improve cookie handling, so that formr works similarly, whether you use study-specific subdomains or not.

## [v0.20.7] - 02.05.2023

### Fixed
* fixed broken redirects to the login page
### Added
* make it easier to dockerise formr
* added a setting to send error logs to stderr
* adapted OpenCPU handling to make it possible to POST (run R commands) to a different URL (e.g., inside a docker network) than where we GET results (e.g., render user-facing feedback). If the old setting base url is used, it should be used for both POST and GET.
* improve cookie handling,
* formr now works similarly, whether you use study-specific subdomains or not.
* cookies are now always valid only for the specific domain on which they were set.
* we now recommend hosting the admin area on a different subdomain than the studies, not on the top level domain.
* removed redundant settings related to cookies from settings.php
* track bower_components to make it easier to collaborate on changes in CSS/JS
* update to halite 5

## [v0.20.8] - 29.11.2023
* remove outdated instructions for self hosting

## [v0.20.7] - 02.05.2023
### Fixed
Expand Down
2 changes: 2 additions & 0 deletions application/Controller/AdminAjaxController.php
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ private function ajaxRunPublicToggle() {
if (!$run->togglePublic($pub)) {
$this->response->setStatusCode(500, 'Bad Request');
}
$content = $this->site->renderAlerts();
return $this->response->setContent($content);
}

private function ajaxSaveRunUnit() {
Expand Down
2 changes: 1 addition & 1 deletion application/Controller/AdminRunController.php
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ private function panicAction() {
$updated = $this->fdb->update('survey_runs', $settings, array('id' => $this->run->id));
if ($updated) {
$msg = array("Panic mode activated for '{$this->run->name}'");
$msg[] = " - Only you can access this run";
$msg[] = " - Only you and test users can access this run";
$msg[] = " - The cron job for this run has been deactivated";
$msg[] = " - The run has been 'locked' for editing";
alert(implode("\n", $msg), 'alert-success');
Expand Down
10 changes: 10 additions & 0 deletions application/Controller/AdminSurveyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ protected function validateUploadedFile($file, $editing = false) {
if (empty($file['name'])) {
return false;
}

// Define the list of allowed extensions
$allowedExtensions = array('xls', 'xlsx', 'ods', 'xml', 'txt', 'csv');
// Get the file extension
$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
// Check if the extension is in the allowed list
if(!in_array($fileExtension, $allowedExtensions)){
alert("<strong>Error:</strong> The format must be one of .xls, .xlsx, .ods, .xml, .txt, or .csv.", 'alert-danger');
return false;
}

$name = preg_filter("/^([a-zA-Z][a-zA-Z0-9_]{2,64})(-[a-z0-9A-Z]+)?\.[a-z]{3,4}$/", "$1", basename($file['name']));
if (!preg_match("/[a-zA-Z][a-zA-Z0-9_]{2,64}/", (string)$name)) {
Expand Down
48 changes: 46 additions & 2 deletions application/Controller/RunController.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,54 @@ public function indexAction($runName = '', $privateAction = null) {
return $this->request->redirect($run_vars['redirect']);
}

$assset_vars = $this->filterAssets($run_vars);
$asset_vars = $this->filterAssets($run_vars);
unset($run_vars['css'], $run_vars['js']);

$this->setView('run/index', array_merge($run_vars, $assset_vars));
$this->setView('run/index', array_merge($run_vars, $asset_vars));

return $this->sendResponse();
}

private function privacyAction() {
$this->run = $this->getRun();
$run_name = $this->site->request->run_name;

if (!$this->run->valid) {
formr_error(404, 'Not Found', 'Requested Run does not exist or has been moved');
}

if(!$this->run->hasPrivacy()) {
$this->request->redirect(run_url($run_name, ''));
}

$run_vars = array(
'run_content' => $this->run->getParsedPrivacyField('privacy-policy'),
'bodyClass' => 'fmr-run',
);

$this->setView('run/static_page', $run_vars);

return $this->sendResponse();
}

private function terms_of_serviceAction() {
$this->run = $this->getRun();
$run_name = $this->site->request->run_name;

if (!$this->run->valid) {
formr_error(404, 'Not Found', 'Requested Run does not exist or has been moved');
}

if(!$this->run->hasToS()) {
$this->request->redirect(run_url($run_name, ''));
}

$run_vars = array(
'run_content' => $this->run->getParsedPrivacyField('terms-of-service'),
'bodyClass' => 'fmr-run',
);

$this->setView('run/static_page', $run_vars);

return $this->sendResponse();
}
Expand Down
76 changes: 74 additions & 2 deletions application/Model/Run.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class Run extends Model {
public $osf_project_id = null;
public $footer_text = null;
public $public_blurb = null;
public $privacy = null;
public $tos = null;
public $use_material_design = false;
public $expire_cookie = 0;
public $expire_cookie_value = 0;
Expand All @@ -62,11 +64,14 @@ class Run extends Model {
protected $description_parsed = null;
protected $footer_text_parsed = null;
protected $public_blurb_parsed = null;
protected $privacy_parsed = null;
protected $tos_parsed = null;
protected $api_secret_hash = null;
protected $owner = null;
protected $run_settings = array(
"header_image_path", "title", "description",
"footer_text", "public_blurb", "custom_css",
"footer_text", "public_blurb", "privacy",
"tos", "custom_css",
"custom_js", "cron_active", "osf_project_id",
"use_material_design", "expire_cookie",
"expire_cookie_value", "expire_cookie_unit",
Expand Down Expand Up @@ -108,7 +113,7 @@ protected function load() {
return;
}

$columns = "id, user_id, created, modified, name, api_secret_hash, public, cron_active, cron_fork, locked, header_image_path, title, description, description_parsed, footer_text, footer_text_parsed, public_blurb, public_blurb_parsed, custom_css_path, custom_js_path, osf_project_id, use_material_design, expire_cookie";
$columns = "id, user_id, created, modified, name, api_secret_hash, public, cron_active, cron_fork, locked, header_image_path, title, description, description_parsed, footer_text, footer_text_parsed, public_blurb, public_blurb_parsed, privacy, privacy_parsed, tos, tos_parsed, custom_css_path, custom_js_path, osf_project_id, use_material_design, expire_cookie";
$where = $this->id ? array('id' => $this->id) : array('name' => $this->name);
$vars = $this->db->findRow('survey_runs', $where, $columns);

Expand Down Expand Up @@ -173,6 +178,12 @@ public function togglePublic($public) {
if (!in_array($public, range(0, 3))) {
return false;
}
$require_privacy = Config::get("require_privacy_policy", false);
if ($require_privacy AND !$this->hasPrivacy() && $public > 0) {
alert('You cannot make this run public because it does not have a privacy policy. Set them first in the settings tab.', 'alert-warning');
$this->db->update('survey_runs', array('public' => 0), array('id' => $this->id));
return false;
}

$updated = $this->db->update('survey_runs', array('public' => $public), array('id' => $this->id));
return $updated !== false;
Expand Down Expand Up @@ -207,6 +218,13 @@ public function create($options) {
$this->name = $name;
$this->load();

$owner = $this->getOwner();
$privacy_url = run_url($name, "privacy");
$tos_url = run_url($name, "terms_of_service");
$settings_url = run_url($name, "settings");
$footer = "Contact the [study administration](mailto:{$owner->email}) in case of questions. [Privacy Policy]($privacy_url). [Terms of Service]($tos_url). [Settings]($settings_url).";
$this->saveSettings(array("footer_text" => $footer));

// create default run service message
$props = RunUnit::getDefaults('ServiceMessagePage');
$unit = RunUnitFactory::make($this, $props)->create();
Expand Down Expand Up @@ -409,6 +427,31 @@ public function getNextPosition($current) {
return null;
}

public function getParsedPrivacyField($field) {
return match ($field) {
'privacy-policy' => $this->privacy_parsed,
'terms-of-service' => $this->tos_parsed,
default => "",
};
}

public function hasPrivacyUnit() {
$select = $this->db->select(array('unit_id'));
$select->from('survey_run_units');
$select->join('survey_units', 'survey_units.id = survey_run_units.unit_id');
$select->where(array('run_id' => $this->id, 'type' => 'Privacy'));

return $select->fetchColumn() !== false;
}

public function hasPrivacy() {
return $this->privacy !== null AND trim($this->privacy) !== '';
}

public function hasToS() {
return $this->tos !== null AND trim($this->tos) !== '';
}

public function getAllUnitTypes() {
$select = $this->db->select(array('survey_run_units.id' => 'run_unit_id', 'unit_id', 'position', 'type', 'description'));
$select->from('survey_run_units');
Expand Down Expand Up @@ -589,6 +632,19 @@ public function saveSettings($posted) {
$posted['footer_text_parsed'] = $parsedown->text($posted['footer_text']);
$this->run_settings[] = 'footer_text_parsed';
}
$require_privacy = Config::get("require_privacy_policy", false);
if ($require_privacy AND ((isset($posted['privacy']) && trim($posted['privacy']) == '')) && $this->public > 0) {
alert("This run is public, but you have removed the privacy policy. We've set it to private for you. Add a privacy policy before setting the run to public again.", 'alert-danger');
$this->db->update('survey_runs', array('public' => 0), array('id' => $this->id));
}
if (isset($posted['privacy'])) {
$posted['privacy_parsed'] = $parsedown->text($posted['privacy']);
$this->run_settings[] = 'privacy_parsed';
}
if (isset($posted['tos'])) {
$posted['tos_parsed'] = $parsedown->text($posted['tos']);
$this->run_settings[] = 'tos_parsed';
}

$cookie_units = array_keys($this->expire_cookie_units);
if (isset($posted['expire_cookie_value']) && is_numeric($posted['expire_cookie_value']) &&
Expand Down Expand Up @@ -855,6 +911,20 @@ public function exec(User $user) {
}
if (!$this->renderedDescAndFooterAlready && !empty($this->footer_text_parsed)) {
$run_content .= $this->footer_text_parsed;
$privacy_pages = [];
$run_url = run_url($this->name) . '?show-privacy-page=';
if ($this->hasPrivacy()) {
$privacy_pages[] = '<a href="' . $run_url . 'privacy-policy" target="_blank">Privacy Policy</a>';
}
if ($this->hasToS()) {
$privacy_pages[] = '<a href="' . $run_url . 'terms-of-service" target="_blank">Terms of Service</a>';
}

if (!empty($privacy_pages)) {
$run_content .= '<br><div class="privacy-footer">';
$run_content .= implode(' | ', $privacy_pages);
$run_content .= '</div>';
}
}

if ($runSession->isTesting()) {
Expand Down Expand Up @@ -908,6 +978,8 @@ public function export($name, array $units, $inc_survey) {
'description' => $this->description,
'footer_text' => $this->footer_text,
'public_blurb' => $this->public_blurb,
'privacy' => $this->privacy,
'tos' => $this->tos,
'cron_active' => (int) $this->cron_active,
'custom_js' => $this->getCustomJS(),
'custom_css' => $this->getCustomCSS(),
Expand Down
Loading

0 comments on commit 54b8eda

Please sign in to comment.