Demo

We deployed our application to Heroku for demo purposes:

Notes:

  • Since we used a free account on the Heroku platform, the website sleeps after 30 mins of inactivity and it must sleep at least 6 hours in a 24 hours period.
  • Due to limitations of the platform we were not able to install OpenOffice on the server. This is because, with OpenOffice installed the application would have been 340 MB and the storage allowance for free accounts on Heroku is of 300MB. Hence, for this deployed version, we disabled the upload of Microsoft Word documents (as the conversion to PDF was handled by OpenOffice). It is still possible to upload all image formats and PDFs though.
  • As this version is for demo purposes only, the payment system still makes use of the Paypal sandbox: no real transaction is performed as the Business account it is linked to is a fictitious one for development purposes only. In order to complete a purchase it is possible to use the account with following details: username = “richaccount@email.com”, password = “createsafe".
  • The email account we made use of as a SMTP relay service is with MailJet, since it is a free account, it is limited to 6000 emails.
  • The website GUI is not fully optimised for mobile rendering
  • Since Heroku provides 10MB free PostgreSQL database, we changed the Doctrine2 settings in order to accommodate the new DBMS instead of MySQL (used in development mode). The size limit on the database should not be an issue for demo purposes.


Implementation Details

Symfony framework

We decided to use the Symfony2 framework. In particular we decided to use version 2.8 since version 3 came out in October/November and documentation and compatibility with third-party bundles was not guaranteed and extensive.


The main benefit we derived from using this framework is the separation of concerns: it is centred around HTTP and it makes use of a MVC architecture in order to allow organised and scalable code. It takes care of low-level utilities and it allowed us to structure the project at a higher level of abstraction making use of its low-level utilities.


The general flow of execution is summarised in the following picture:



Requests are routed and passed to the correct Controller in charge of handling the Request and returning a Response object (whether it is a rendered template or some other response such as json or other). The framework doesn’t itself provide a Model part, but it is tightly integrated with Object Relational Mapper tools such as Doctrine2 (which we used to model the database of the application).


Doctrine2 allows separation of the website logic from the persistence in the relational database management system (during development we used MySQL); in particular it allows to treat database entities as regular PHP objects and it takes care of mapping the PHP class to the database tables and vice versa. The rules of this mapping can be easily specified making use of special annotations.



Furthermore, Symfony provides integration with open source tools (so as to avoid "reinventing the wheel") such as:


Assetic
It provides one more layer of abstraction between static files such as CSS, Javascript and images and the application. Serving of the files is handled by Assetic and it is not directly managed by the application.
Monolog
It eases the logging of various messages and error. Its use is fundamental in the deployment when the application cannot be run in debug mode.
Swiftmailer
Based on the PHP Swift Mailer library, it allows a Object-Oriented approach to sending emails. Compliant with the SMTP protocol, we used it to send emails for our web application.
Composer
Dependency management tool for PHP that takes care of installing and updating third-party libraries.

Login and Registration

We made use of the FriendsOfSymfonyUserBundle in order to implement registration and login. We extended the BaseUser class and added First Name and Last Name attributes; this required fixing the forms for login and registration. We also overwrote most of the template in the view folder.


The bundle already provided an implementation for the profile page, but in order to accommodate for new features, we had to extend the bundle, which allowed us to extend the ProfileController and add new actions to deal with the new features (such retrieving of protected works, Cease and Desist letter and others). The Bundle is integrated with the SwiftMailer Bundle in order to provide email facilities for registration and password resetting (adequately overwritten).

File Upload

The file upload is handled by the OneupUploaderBundle, which provides a backend for the javascript library we used for the front-end of the file upload functionality. In particular, we used Dropzone.js because of its high customisation. As highlighted in the prototype section, the Orphanage functionality lets us gracefully manage files uploaded by anonymous users until successful payment.

Paypal Express Payment

We utilised the PayumBundle that allowed us to easily implement the payment system integrated with Paypal. For development purposes, Paypal allows the creation of a Sandbox, Business and Personal accounts in order to recreate the production environment and process flow without necessarily deploying the application. API requests and responses are handled according to the following sequence diagram:



All these methods are contained in the PaymentController class, responsible for handling of the payment process and finalising upload of the files and watermarking.


The payment method renders the payment page if the user has uploaded some files, it then gives control to the prepareAction that prepares the payment calculating the total (5£ per file, as specified by our client). Capture (within the Payum Bundle) handles the token and redirects to the paypal.com website (in the development as well as the demo, we set up a developer Business and Personal accounts to use the Paypal sandbox). Paypal then returns a token with either a successful payment or failure. If the payment was rejected an appropriate message is displayed to the user and this is redirected to the profile page.

Final File Upload

If the payment is successful, the doneAction takes care of finalising the upload of the files making use of the VichUploaderBundle (Symfony2 bundle that eases file uploads that are attached to ORM entities). The bundle handles the linking of the file (we appropriately modified the class to represent it as to accommodate our specific requirements) with the database (used also for the removing functionality available from the profile page).


Files are moved to the permanent storage folder: under the “protected” folder, each user gets allocated a folder (on first upload) based on its user id (automatically allocated by DBMS). After completion of the upload the user gets redirected to a successful feedback message and then to its profile page.

Watermarking

Images

In order to stamp .jpeg and .png format images, we used Imagick which is the php extension to make or change images using the PHP ImageMagick API. When stamping the images, there are 3 steps:

  1. Generate an image of the certification watermark including registration number and certified date. The system get the current year and the date (UTC). Next, by using Imagick, the text to write is generated together with the size and the colour of the font, and the font to use. We then align the text to the centre and save the image temporary as “cert_(serial code)_.png”.
  2. Get the size of the original image, and resize the watermark made in Step1 to fit it. We used the Imagick function “getImageWidth()” and “getImageHeight()” to get the size of the image in pixels. According to the shape of the image (whether the height is longer than the width, or opposite), set the size of the watermark and certification text into 1/3 of the shorter side in order to fit the watermark to the image and stay inside the image.
  3. Compose the watermark and the CreateSafe logo image on top of the original. The watermark image is placed at the centre of the right hand side, and merged with the certification text right below it. Then write this image as an output and save it in the user protected folder. Before moving the final image, it is compressed in order to save space on the server (it is not a goal of the target to provide a cloud backup).

Word documents and PDFs

For PDFs, we used TCPDF and FPDI: the former one allows to create and edit PDF files whereas the latter imports PDF files instead of generating a new one. In the same way as image stamping, the system first gets the current date for the certification, it then imports the first page and decides which side (width or height) is longer. According to that result, it composes the watermark and certification on the first page. Since this process is inside the “for loop”, it is repeated until the last page is imported and stamped.

After stamping all the pages, the system generates an image form the first page of the document to be used as a thumbnail in the users’ personal pages.


DOCX documents are converted to PDF using OpenOffice. Afterwards, the PDF gets stamped as described above.


Object-Oriented Design Patterns

In the implementation of the website we made use of different design patterns:


MVC (Model-View-Controller)
The Symfony2 frameworks is based on the Model View Controller abstraction where the Controller (same name as in the framework) updates the view (templates in Symfony2 represented by .html.twig files) according to the model (this is not natively integrated in the framework but it is handled by the Doctrine2 library that allows integration with the database). It clearly is adapted to the Web needs and built around the HTTP specifications (so a controller returns a Response rather than necessarily updating the View) but the underlying logic can be easily recognised. It is very useful as responsibilities are assigned according to each component.
Strategy
Generation of the registration number is performed by the HashingRegistrationNumber php class. This is a concrete class that implements the RegistrationNumberCreator interface. This is in order to allow subsequent modifications of the algorithm. Adding a different algorithm for generating the registration number won’t break the code as long as it implements the strategy interface, and it can therefore easily replace the current implementation. This is to enhance scalability and refactoring. The concrete class is registered as a service and hence it just needs to be replaced in the “services.yml” file. No additional steps are required.
Observer
The whole framework is event-driven and implementation of the Observer pattern is heavily used whenever an event is fired. We personally implemented the pattern with the UploadListener that is registered in the services.yml to respond to the "oneup_uploader.post_upload" event. This is, hence, called every time a user uploads a new file, in order to rename the file and keep track of the original names of the files (this is needed to override the OneupUploader default behaviour of renaming files upon successful upload).
Front controller
It "provides a centralized entry point for handling requests." [Alur, Deepak; John Crup; Dan Malks (2003). Core J2EE Patterns, Best Practices and Design Strategies, 2nd Ed. Sun Microsystems Press. pp. 650pp.] This is part of the Symfony2 framework implementation. Refer to Symfony2 section.

Testing

During both unit and functional testing we made use of most of the utilities offered by PHPUnit.


Uniqueness for Registration Number

Since the registration number has to be unique for each file registered on the website, we created a test case in order to assert uniqueness of the numbers generated. There are other safety mechanisms in our code that perform checks in order to guarantee a unique number (this include enforcing uniqueness through database constraints on the entity attribute and regenerating the number in case it is found not to be unique).

This test, as well as others makes use of the getMockBuilder() function of PHPUnit. This is needed in order to be able to target testing uniquely to the functionality that is being tested and remove any dependency to other components that may have side effects; it may be also used to mock objects that are simply unavailable in the test environments since not the whole application is running. This is a key feature of unit testing. We made use of this feature many times during automated testing.

For the RegistrationNumberTest class, in the setUp we mocked the TokenStorage needed by the HashingRegistrationNumber class and its dependencies up to the User object needed in order to generate the number:


public function setUp() {
	$this->client = static::createClient();
	$this->container = $this->client->getContainer();
	$mockToken = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')
		->disableOriginalConstructor()
		->getMock();
	$this->mockUser = $this->getMockBuilder('AppBundle\Entity\User')
		->disableOriginalConstructor()
		->getMock();
	$this->changeUsername();
	$mockToken->expects($this->any())->method('getUser')->will($this->returnValue($this->mockUser));
	$mockTokenStorage = $this->getMockBuilder('\Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage')
		->disableOriginalConstructor()
		->getMock();
	$mockTokenStorage->expects($this->any())->method('getToken')->will($this->returnValue($mockToken));
	$this->generator = new \AppBundle\Services\HashingRegistrationNumber($mockTokenStorage);
}

Uniqueness is then asserted in the testUniqueness() method that asserts uniqueness for each number up to 12500 times (25 different files for 500 different users; increasing the number of assertions is possible but it slows down testing since mocking is not a fast procedure):


public function testUniqueness() {
	$numbers = array();
	for($j = 0; $j<500; $j++) {
		for ($i = 0; $i < 25; $i++) {
			$registrationNumber = $this->generator->getUniqueRegistrationNumber();
			$this->assertNotContains($registrationNumber, $numbers);
			$numbers[] = $registrationNumber;
		}
		$this->changeUsername();
	}
}

Functional Testing

In order to test various functionalities of the website and constraints on the flow of execution, we performed functional testing. Again, it was performed using PHPUnit and its features for mocking objects and “crawling” and recreating the User’s actions.

In our tests we tested successful login (which required us to create a separate helping class FakeLogin in order to create a fictitious User to be able to Login):


/**
 * Test successful Login
 */
public function testLogin() {
	$this->client = static::createClient();
	$crawler = $this->client->request('GET', '/login');

	$this->assertTrue($this->client->getResponse()->isSuccessful());

	$user = FakeLogin::getUser($this->container);
	$form = $crawler->selectButton('Login')->form(array('_username' => 'user@test.test', '_password' => 'user'));
	$this->client->submit($form);

	$homepageUrl = $this->container->get('router')->generate('homepage', [], $referenceType = UrlGeneratorInterface::ABSOLUTE_URL);
	$this->assertTrue($this->client->getResponse()->isRedirect($homepageUrl));

	$crawler = $this->client->followRedirect();
	$this->assertContains('Copyright', $crawler->filter('h1')->text());
	FakeLogin::removeUser($user, $this->container);
}

We also tested login with invalid credentials which should display a message accordingly:


/**
 * Test that trying to Login with Invalid credentials displays alert.
 */
public function testLoginInvalidCredentials() {
	$crawler = $this->client->request('GET', '/login');

	//user has not been created. Should fail.
	$form = $crawler->selectButton('Login')->form(array('_username' => 'user@test.test', '_password' => 'user'));
	$this->client->submit($form);

	$loginUrl = $this->container->get('router')->generate('fos_user_security_login', [], $referenceType = UrlGeneratorInterface::ABSOLUTE_URL);
	$this->assertTrue($this->client->getResponse()->isRedirect($loginUrl));

	$crawler = $this->client->followRedirect();
	$this->assertContains('Invalid credentials.', $crawler->filter('.alert')->children()->filter('p')->text());
}

We tested successful registration:


/**
 * Test successful registration
 */
public function testRegistration() {
	$crawler = $this->client->request('GET', '/register/');
	$form = $crawler->selectButton('Register')->form();
	$values = $form->getValues();
	$values["fos_user_registration_form[email]"] = 'user@test.test';
	$values["fos_user_registration_form[plainPassword][first]"] = 'user';
	$values["fos_user_registration_form[plainPassword][second]"] ='user';
	$values["fos_user_registration_form[firstName]"] = 'First';
	$values["fos_user_registration_form[lastName]"] = 'Last';
	$form->setValues($values);
	$this->client->submit($form);
	$this->assertTrue($this->client->getResponse()->isRedirect());
	$user = FakeLogin::getUser($this->container);
	FakeLogin::removeUser($user, $this->container);
}

Further tests are performed by other classes, as, for example, on the payment page in order to assert the presence of files when accessing the page.

Periodically, during the development, whenever we modified the code we could quickly run our tests to assert that we did not break any other part of the project. Automated testing helped us in the development process to locate bugs and fix them.




Usability Testing

In order to guarantee to meet the quality standards we set for the project, after developing the platform we performed some usability testing against the Use Cases we identified during the first term.

This allowed us to identify possible usability issues as well as design flaws of the systems.

We performed 5 different User testing sessions with 5 different potential users. All feedback received were categorised according to 5 different ratings:

  • Low: small impact on user experience, fix this if time allows.
  • Medium: Noticeable impact on user experience, but does not prevent task completion. Fixing this will increase satisfaction.
  • High: Prevents task completion. Should be fixes.
  • Critical: Prevents users from using a key feature of your product. Imperative to fix before product can be released.
  • Good: Good practice/positive finding – no action needed

Login and Registration
  • Description: Users were asked to perform Registration and Login processes.
  • Use cases covered: UC1, UC1.1, UC2, UC2.1
  • Outcome:
    • G: No relevant issues identified with these use cases, all the users reported a positive feedback on the login and registration flow
    • G: Invalid Credentials issues were appropriately reported to the user with a visual feedback.

Uploading Files
  • Description: Users were asked to upload files using the platform.
  • Use cases covered: UC3a, UC3.1, UC3.2
  • Outcome:
    • G: The upload system is easy to use and intuitive and accessible from all pages of the website.
    • L: Even though feedback is provided when a file with an unsupported extension is uploaded, there is no list to know beforehand exactly which files are supported.
    • G: The systems feedbacks the user on the upload progress with a progress bar, plus it has thumbnails for images.

Payment
  • Description: Users were asked to complete the process by paying via Paypal and access their profile pages.
  • Use cases covered: UC4, UC4.1, UC4.2
  • Outcome:
    • G: the process is quick.
    • G: the confirmation email with registration numbers has a good design.
    • M: when uploading files, if you are not registered, after registering you are not redirected to payment since registration requires confirmation via link on registration email; hence upload process has to be started over again. (this issue is not present if just login is required) Note: Due to system design/client requirements, it was not possible to fix the problem otherwise. Accounts need to be verified before users allowed to fully login and payment requires full login.

Update User Details
  • Description: Users were asked to change their personal details.
  • Use cases covered: UC5
  • Outcome:
    • L: design could be improved.
    • G: no issues encountered, easy and intuitive.

Download and Printing Protected Document
  • Description: Users were asked to download or print a copy of their uploaded file.
  • Use cases covered: UC8
  • Outcome:
    • G: the process is easy and intuitive.

Cease and Desist Letter
  • Description: Users were asked to send a Cease and Desist Letter.
  • Use cases covered: UC7
  • Outcome:
    • G: Very positive feedbacks, easy to use and useful.


Final notes on usability tests:

  • No Critical or High issues were highlighted from our user tests.
  • More functionalities are available to the website, but not all of them were tested (e.g. invite a friend).
  • Use cases not covered:
    • 3b: not implemented, we prioritised other features.
    • 6: Payment is only available though Paypal, this Use cases could not be covered.
    • 9, 10, 11, 12: these requirements were dropped after agreement with our client. Please see relevant section for further details.