diff --git a/docs/install.md b/docs/install.md
index 512703154..27fbffe86 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -24,10 +24,15 @@ The codespace includes Mailhog, which will trap any emails sent by MyRadio. To s
click the Local Address next to port 8025 in the Ports panel.
## Docker Install
+
If you have Docker on your system, use Docker Compose to set up an environment.
-Simply run `docker compose up -d`, and visit "https://localhost:4443/myradio/".
+Simply run `docker compose up -d` and visit "https://localhost:4443/myradio/".
+
+If you encounter an error with autoload.php:
+Simply run the following docker command `docker compose exec myradio composer install`
## Vagrant Install
+
MyRadio comes with a Vagrantfile based on Ubuntu 19.10.
If you have [Vagrant](https://www.vagrantup.com) installed and want to get
developing or playing right away, just run `vagrant up` and a few minutes
@@ -43,8 +48,9 @@ so be sure to never run this in a production environment, or remove the
permission before doing so.
## Uncontained Install
+
Install Apache2, PHP, Composer and PostgreSQL on your prefered Unix-based distro.
-Or Windows, if you're into that.
+Or Windows, if you're into that.
MyRadio has been tested with Ubuntu and FreeBSD.
cd to your MyRadio installation and run `composer install`
@@ -74,6 +80,7 @@ Alias /api /usr/local/www/MyRadio/src/PublicAPI
Restart Apache2, go to http://hostname/myradio
To make a new postgresql server, run the following after:
+
```
pg_createcluster [YOUR_POSTGRES_VERSION] myradio
su postgres
@@ -85,36 +92,47 @@ CREATE DATABASE myradio WITH OWNER=myradio;
# Post-Installation
## Myradio Setup
+
CONNECT:
- - Open up "https://localhost:4443/myradio/" in a browser
- - [Use Chrome as this often fails to run on Firefox]
- - It will say "connection not private" so press "advanced" and then "proceed"
+
+- Open up "https://localhost:4443/myradio/" in a browser
+- [Use Chrome as this often fails to run on Firefox]
+- It will say "connection not private" so press "advanced" and then "proceed"
DATABASE:
- - On the intro screen press "Click here to continue"
- - Enter the database details (see Default Credentials) and press Next
- - Press "run task", wait a few seconds and then press "run task" again.
- - [This method is a workaround for a slight bug in how we build the database]
-
+
+- On the intro screen press "Click here to continue"
+- Enter the database details (see Default Credentials) and press Next
+- Press "run task", wait a few seconds and then press "run task" again.
+- [This method is a workaround for a slight bug in how we build the database]
+
USER:
- - [Here you can make config changes but the defaults are autofilled]
- - Press "complete starting set", scroll and press "save and continue"
- - Input any first and last name, an email (NOT an @york.ac.uk email) and a password
- - [If you enter an @york.ac.uk email you will not be able to login at all]
- - Login using the email and password you just enterted
+
+- [Here you can make config changes but the defaults are autofilled]
+- Press "complete starting set", scroll and press "save and continue"
+- Input any first and last name, an email (NOT an @york.ac.uk email) and a password
+- [If you enter an @york.ac.uk email you will not be able to login at all]
+- Login using the email and password you just enterted
+
+- If you encounter an error you need to create the file /var/www/myradio/src/MyRadio_Config.local.php with the text from the "Show Config" button
+- [if you are using docker this needs to be done within the docker container using `docker exec -it [myradioid] bash`]
## Default Credentials
+
Database: (when building the database, these credentials are needed)
- - Hostname: `postgres` if running in Docker, `localhost` otherwise
- - Database: myradio
- - Username: myradio
- - Password: myradio
+
+- Hostname: `postgres` if running in Docker, `localhost` otherwise
+- Database: myradio
+- Username: myradio
+- Password: myradio
Vagrant VM: (if you need to ssh into the virtual machine)
- - Username: vagrant
- - Password: vagrant
+
+- Username: vagrant
+- Password: vagrant
## Tests
+
MyRadio uses [Codeception](http://codeception.com/quickstart) for its test suite.
[This was written with a Vagrant install in mind - has not been tested on Docker]
@@ -132,10 +150,11 @@ blanks the `myradio_test` database each time it is ran, so it can be used to
reset the database and config file, should this prove necessary.
Summary:
-* `composer install`
-* `vagrant up`
-* `vagrant ssh -- /vagrant/scripts/reset-db.sh`
-* `src/vendor/bin/codecept run`
+
+- `composer install`
+- `vagrant up`
+- `vagrant ssh -- /vagrant/scripts/reset-db.sh`
+- `src/vendor/bin/codecept run`
The vagrant initialisation script also runs `composer install`, but that is run
on the virtual machine which also installs the PHP extensions required for
@@ -143,17 +162,18 @@ Codeception. These extensions may be missing locally, so running composer will
confirm that they are present.
## Next Steps
+
Once you've got through the setup wizard, the next thing that's most useful to
you is most likely creating a show.
To do this, you first need to:
-- Create a Term (Show Scheduler -> Manage Terms)
-- Create a Show (List My Shows -> Create a Show)
-- Apply for a Season of your new Show (List My Shows -> New Season)
-- Schedule the Season (Shows Scheduler)
-### A note on Seasons and Terms
-MyRadio splits Shows into "Seasons". Any Season is applied to in relation to a
-"Term", which is a 10-week space of time. This is because The University of
-York has 10 week terms, if you didn't know.
+- Create a Term (Show Scheduler -> Manage Terms)
+- Create a Show (List My Shows -> Create a Show)
+- Apply for a Season of your new Show (List My Shows -> New Season)
+- Schedule the Season (Shows Scheduler)
+#### A note on Seasons and Terms
+
+MyRadio splits Shows into "Seasons". Any Season is applied to in relation to a
+"Term", which is a user defined space of time (normally 11-15 weeks). This is because The University of York has 12 week semesters, if you didn't know.
diff --git a/schema/api.graphql b/schema/api.graphql
index 1c326e09c..479c9ca74 100644
--- a/schema/api.graphql
+++ b/schema/api.graphql
@@ -99,6 +99,7 @@ type User implements Node & MyRadioObject {
id: ID! @bind(method: "getID")
itemId: Int! @bind(method: "getID")
fname: String! @bind(method: "getFName")
+ nname: String! @bind(method: "getNName")
sname: String! @bind(method: "getSName")
# Public information
@@ -393,6 +394,7 @@ type EmailDestination {
type MemberSearchResult {
memberid: Int!
fname: String!
+ nname: String!
sname: String!
eduroam: String
local_alias: String
diff --git a/schema/api.json b/schema/api.json
index 9ebd09a0e..c40e0e728 100644
--- a/schema/api.json
+++ b/schema/api.json
@@ -117,6 +117,9 @@
"fname": {
"type": "string"
},
+ "nname": {
+ "type": "string"
+ },
"sname": {
"type": "string"
},
diff --git a/schema/patches/19.sql b/schema/patches/19.sql
new file mode 100644
index 000000000..5639051a8
--- /dev/null
+++ b/schema/patches/19.sql
@@ -0,0 +1,5 @@
+ALTER TABLE member
+ ADD COLUMN nname character varying(255);
+
+INSERT INTO metadata.metadata_key VALUES (20,'upload_starttime',false,'In the case where a manual upload is required (because an event started late) will need to start late. This is a UTC time.',300,false);
+INSERT INTO metadata.metadata_key VALUES (21,'upload_endtime',false,'In the case where a manual upload is required (because an event started late) will need to finish early/late.',300,false);
diff --git a/scripts/gdprdeleteuser.php b/scripts/gdprdeleteuser.php
index 00ac3618d..9d6225fee 100644
--- a/scripts/gdprdeleteuser.php
+++ b/scripts/gdprdeleteuser.php
@@ -40,8 +40,8 @@
try{
$db->query(
'INSERT INTO public.member(
- memberid, fname, sname, college, receive_email, data_removal)
- VALUES ($1, \'deleted\', \'user\', 10, false, \'deleted\')',
+ memberid, fname, nname, sname, college, receive_email, data_removal)
+ VALUES ($1, \'deleted\', \'\' ,\'user\', 10, false, \'deleted\')',
[$deletedUserId]
);
} catch (exception $e) {
diff --git a/src/Classes/MyRadio/MyRadioNews.php b/src/Classes/MyRadio/MyRadioNews.php
index e607982dc..9b5b75dbe 100644
--- a/src/Classes/MyRadio/MyRadioNews.php
+++ b/src/Classes/MyRadio/MyRadioNews.php
@@ -75,7 +75,11 @@ public static function getNewsItem($newsentryid, MyRadio_User $user = null)
$db = Database::getInstance();
$news = $db->fetchOne(
- 'SELECT newsentryid, fname || \' \' || sname AS author, timestamp AS posted, content
+ 'SELECT newsentryid,
+ CASE WHEN nname IS NULL
+ THEN fname || \' \' || sname
+ ELSE fname || \' "\' || nname || \'" \' || sname
+ END AS name, timestamp AS posted, content
FROM public.news_feed, public.member
WHERE newsentryid=$1
AND news_feed.memberid = member.memberid',
diff --git a/src/Classes/ServiceAPI/MyRadio_User.php b/src/Classes/ServiceAPI/MyRadio_User.php
index 6c6702732..31f9c8757 100755
--- a/src/Classes/ServiceAPI/MyRadio_User.php
+++ b/src/Classes/ServiceAPI/MyRadio_User.php
@@ -47,6 +47,13 @@ class MyRadio_User extends ServiceAPI implements APICaller
*/
private $fname;
+ /**
+ * Stores the User's nickname.
+ *
+ * @var string
+ */
+ private $nname;
+
/**
* Stores the User's last name.
*
@@ -237,7 +244,7 @@ protected function __construct($memberid)
$this->memberid = (int) $memberid;
//Get the base data
$data = self::$db->fetchOne(
- 'SELECT fname, sname, college AS collegeid, l_college.descr AS college,
+ 'SELECT fname, nname, sname, college AS collegeid, l_college.descr AS college,
phone, email, receive_email::boolean::text, local_name, local_alias, eduroam,
account_locked::boolean::text, last_login, joined, profile_photo, bio,
auth_provider, require_password_change::boolean::text, contract_signed::boolean::text, gdpr_accepted::boolean::text,
@@ -434,6 +441,18 @@ public function getFName()
return $this->fname;
}
+ /**
+ * Returns the User's nickname.
+ *
+ * @return string The User's nickname
+ */
+ public function getNName()
+ {
+ return $this->nname;
+ }
+
+
+
/**
* Returns the User's surname.
*
@@ -451,7 +470,11 @@ public function getSName()
*/
public function getName()
{
+ if (!empty($this->nname)) {
+ return $this->fname.' "'.$this->nname.'" '.$this->sname;
+ }
return $this->fname.' '.$this->sname;
+
}
public function getLastLogin()
@@ -930,14 +953,14 @@ public static function findByName($name, $limit = -1)
$names = explode(' ', $name);
if (isset($names[1])) {
return self::$db->fetchAll(
- 'SELECT memberid, fname, sname, eduroam, local_alias FROM member
+ 'SELECT memberid, fname, nname, sname, eduroam, local_alias FROM member
WHERE fname ILIKE $1 || \'%\' AND sname ILIKE $2 || \'%\'
ORDER BY sname, fname LIMIT $3',
[$names[0], $names[1], $limit]
);
} else {
return self::$db->fetchAll(
- 'SELECT memberid, fname, sname, eduroam, local_alias FROM member
+ 'SELECT memberid, fname, nname, sname, eduroam, local_alias FROM member
WHERE fname ILIKE $1 || \'%\' OR sname ILIKE $1 || \'%\'
ORDER BY sname, fname LIMIT $2',
[$name, $limit]
@@ -1288,6 +1311,14 @@ public function setFName($fname)
return $this;
}
+ public function setNName($nname)
+ {
+
+ $this->setCommonParam('nname', $nname);
+
+ return $this;
+ }
+
/**
* Set the User's official @ury.org.uk prefix. Usually fname.sname.
*
@@ -1666,6 +1697,17 @@ public function getEditForm()
]
)
)
+ ->addField(
+ new MyRadioFormField(
+ 'nname',
+ MyRadioFormField::TYPE_TEXT,
+ [
+ 'required' => false,
+ 'label' => 'Nickname',
+ 'value' => $this->getNName(),
+ ]
+ )
+ )
->addField(
new MyRadioFormField(
'sname',
@@ -1761,7 +1803,7 @@ public function getEditForm()
MyRadioFormField::TYPE_EMAIL,
[
'required' => false,
- 'label' => 'Email',
+ 'label' => 'Public Email',
'value' => $this->email,
]
)
@@ -2065,6 +2107,7 @@ public function activateMemberThisYear($paid = 0)
* Creates a new User, or activates a user, if it already exists.
*
* @param string $fname The User's first name.
+
* @param string $sname The User's last name.
* @param string $eduroam The User's @york.ac.uk address.
* @param int $collegeid The User's college.
@@ -2079,6 +2122,7 @@ public function activateMemberThisYear($paid = 0)
*/
public static function createOrActivate(
$fname,
+
$sname,
$eduroam = null,
$collegeid = null,
@@ -2098,6 +2142,7 @@ public static function createOrActivate(
} else {
$data = [
'fname' => $fname,
+
'sname' => $sname,
'eduroam' => $eduroam,
'collegeid' => $collegeid,
@@ -2124,6 +2169,7 @@ public static function createOrActivate(
*/
public static function createActivateAPI(
$fname,
+
$sname,
$captcha,
$eduroam = null,
@@ -2352,6 +2398,7 @@ public function getEmptyData(){
$data['bio'] = 'This user is hidden';
$data['memberid'] = $this->getID();
$data['fname'] = 'Hidden';
+ $data['nname'] = NULL;
$datap['sname'] = 'User';
$data['public_email'] = '';
$data['url'] = $this->getURL();
@@ -2407,6 +2454,7 @@ public function toDataSource($mixins = [])
$data = [
'memberid' => $this->getID(),
'fname' => $this->getFName(),
+ 'nname' => $this->getNName(),
'sname' => $this->getSName(),
'public_email' => $this->getPublicEmail(),
'url' => $this->getURL(),
diff --git a/src/Classes/ServiceAPI/Profile.php b/src/Classes/ServiceAPI/Profile.php
index ccd96a4a7..df98cbf40 100644
--- a/src/Classes/ServiceAPI/Profile.php
+++ b/src/Classes/ServiceAPI/Profile.php
@@ -64,22 +64,30 @@ public static function getThisYearsMembers()
* a member, sorted by their name:
*
* memberid: The user's unique memberid
- * name: The user's last and first names formatted as sname, fname
+ * name: The user's last and first names and possibly nickname formatted as fname "nname" sname
* college: The name of the member's college (not the ID!)
* paid: How much the member has paid this year
*/
public static function getMembersForYear($year)
{
self::wakeup();
+
- return self::$db->fetchAll(
- 'SELECT member.memberid, sname || \', \' || fname AS name, l_college.descr AS college, paid, email, eduroam
+ $result = self::$db->fetchAll(
+ 'SELECT member.memberid,
+ CASE WHEN nname IS NULL
+ THEN fname || \' \' || sname
+ ELSE fname || \' "\' || nname || \'" \' || sname
+ END AS name, l_college.descr AS college, paid, email, eduroam
FROM member INNER JOIN (SELECT * FROM member_year WHERE year = $1) AS member_year
ON ( member.memberid = member_year.memberid ), l_college
WHERE member.college = l_college.collegeid
ORDER BY sname ASC',
[$year]
);
+
+
+ return $result;
}
/**
@@ -101,8 +109,11 @@ public static function getCurrentOfficers()
if (self::$currentOfficers === false) {
self::wakeup();
self::$currentOfficers = self::$db->fetchAll(
- 'SELECT team.team_name AS team, officer.officer_name AS officership,
- sname || \', \' || fname AS name, member.memberid
+ 'SELECT team.team_name AS team, officer.officer_name AS officership
+ CASE WHEN nname IS NULL
+ THEN fname || \' \' || sname
+ ELSE fname || \' "\' || nname || \'" \' || sname
+ END AS name, member.memberid
FROM member, officer, member_officer, team
WHERE member_officer.memberid = member.memberid
AND officer.officerid = member_officer.officerid
@@ -136,7 +147,10 @@ public static function getOfficers()
self::wakeup();
self::$officers = self::$db->fetchAll(
'SELECT team.team_name AS team, officer.type, officer.officer_name AS officership,
- fname || \' \' || sname AS name, member.memberid, officer.officerid
+ CASE WHEN nname IS NULL
+ THEN fname || \' \' || sname
+ ELSE fname || \' "\' || nname || \'" \' || sname
+ END AS name, member.memberid, officer.officerid
FROM team
LEFT JOIN officer ON team.teamid = officer.teamid AND officer.status = \'c\'
LEFT JOIN member_officer ON officer.officerid = member_officer.officerid
diff --git a/src/Controllers/Profile/edit.php b/src/Controllers/Profile/edit.php
index 6a688da0e..553a8cfad 100644
--- a/src/Controllers/Profile/edit.php
+++ b/src/Controllers/Profile/edit.php
@@ -22,6 +22,7 @@
$data = $user->getEditForm()->readValues();
$user->setFName($data['fname'])
+ ->setNName($data['nname'])
->setSName($data['sname'])
->setCollegeID($data['collegeid'])
->setPhone($data['phone'])
diff --git a/src/Controllers/root.php b/src/Controllers/root.php
index afd0adef0..9ad288c7d 100644
--- a/src/Controllers/root.php
+++ b/src/Controllers/root.php
@@ -13,7 +13,7 @@
* This number is incremented every time a database patch is released.
* Patches are scripts in schema/patches.
*/
-define('MYRADIO_CURRENT_SCHEMA_VERSION', 18);
+define('MYRADIO_CURRENT_SCHEMA_VERSION', 19);
/*
* Turn on Error Reporting for the start. Once the Config object is loaded
diff --git a/src/Public/js/myradio.form.js b/src/Public/js/myradio.form.js
index b5ab54ed0..8029a022c 100644
--- a/src/Public/js/myradio.form.js
+++ b/src/Public/js/myradio.form.js
@@ -59,7 +59,10 @@ var MyRadioForm = {
},
{
displayKey: function (i) {
- return i.fname + " " + i.sname;
+ if (i.nname != null) {
+ return i.fname + ' "' + i.nname + '" ' + i.sname;
+ }
+ return i.fname + ' ' + i.sname;
},
source: memberLookup.ttAdapter(),
templates: {
@@ -76,6 +79,9 @@ var MyRadioForm = {
} else {
identity = "(#" + i.memberid + ")";
}
+ if (i.nname != null) {
+ return $("
").text(i.fname + ' "' + i.nname + '" ' + i.sname + " " + identity); + } return $("
").text(i.fname + " " + i.sname + " " + identity);
}
}
@@ -83,6 +89,7 @@ var MyRadioForm = {
.on(
"typeahead:selected",
function (e, obj) {
+
idField.val(obj.memberid);
}
);
diff --git a/src/Public/js/myradio.timeslot.js b/src/Public/js/myradio.timeslot.js
index 03b3c655d..2095e3f9e 100644
--- a/src/Public/js/myradio.timeslot.js
+++ b/src/Public/js/myradio.timeslot.js
@@ -1,3 +1,11 @@
+function formatName(data) {
+ if (empty(data[row].user.nname) != false) {
+ return data[row].user.fname + ' "' + data[row].user.nname + '" ' + data[row].user.sname;
+ }
+ return data[row].user.fname + " " + data[row].user.sname;
+}
+
+
/* global moment, myradio */
/**
* Handles the interactivityness of timeslot selection
@@ -70,11 +78,11 @@ $("#timeslots").on(
.attr("id", "signin_"+data[row].user.memberid)
.attr("value", data[row].user.memberid);
label.attr("for", "signin_"+data[row].user.memberid)
- .html(data[row].user.fname + " " + data[row].user.sname);
+ .html(formatName(data));
if (data[row].signedby !== null) {
check.attr("checked", "checked")
.attr("disabled", "true");
- label.append(" (Signed in by "+data[row].signedby.fname + " "+data[row].signedby.sname + ")");
+ label.append(" (Signed in by " + formatName(data[row].signedby) + ")");
} else if (data[row].user.memberid == window.myradio.memberid) {
check.attr("checked", "checked");
}
@@ -88,7 +96,7 @@ $("#timeslots").on(
}
$("#guest-signins").append(
$("")
- .text(data[row].signedby.fname + " " + data[row].signedby.sname
+ .text(formatName(data[row])
+ " (" + moment.unix(data[row].time).fromNow() + ")")
.append(" To finish up, tell us a little about yourself so we can create you an account.
")
);
diff --git a/src/Templates/Profile/user.twig b/src/Templates/Profile/user.twig
index bf8dc8efc..f5d8ed57d 100644
--- a/src/Templates/Profile/user.twig
+++ b/src/Templates/Profile/user.twig
@@ -13,7 +13,7 @@
{% if user.college is defined %}
diff --git a/src/Templates/Setup/user.twig b/src/Templates/Setup/user.twig
index 6503a7529..588e58fc3 100644
--- a/src/Templates/Setup/user.twig
+++ b/src/Templates/Setup/user.twig
@@ -3,24 +3,24 @@
Name:
- {{user.fname}} {{user.sname}}
+ {{user.fname}} {{user.nname}} {{user.sname}}