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("
") ); 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 @@ Name: - {{user.fname}} {{user.sname}} + {{user.fname}} {{user.nname}} {{user.sname}} {% 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 @@

To finish up, tell us a little about yourself so we can create you an account.

-
- -
-
- -
- -
-
- -
- -
{{pass_error}} -
+
+ +
+
+ +
+ +
+
+ +
+ +
{{pass_error}} +
{% endblock %}