Tutorial

In this tutorial, we will create the DocuVault file management system, which you can find fully coded in htdocs/tutorial.

Requirements

DocuVault is a very simple demo of a document management system designed to facilitate work between legal or auditing firms and their clients.

The company administrator can define new users and file types (contracts, deeds, invoices, etc.).

Clients can upload their files and categorize them. The company (administrators) can view files from all clients;

clients can only view their own files.

Setup

If you haven't already, follow the steps in the "Quick Start" guide to clone the repository and set up a development environment with Simplon PHP.

Make a copy of htdocs/blank and rename it to htdocs/docuvault.

In the lines:

5     Set the app name.

$AppName = 'docuvault';

38   'DEFAULT_ELEMENT' => 'AE_File',

39   'DEFAULT_METHOD' => 'showAdmin',

* If you are using Docker as indicated in the quick start guide, that's all.

In general, variables have self-explanatory names that you can modify according to your needs. We highlight the following lines:

33   The DB parameters.

8 and 9   The location of the Simplon base files.

15   The renderer.

21   The web address of the application.

29   The application language.

35   The time zone.

First Elements

From the description of our app, we can see that we will have:

First, we will create the basic functionality and then add permissions, so we will create the classes corresponding to files and file types.

Let's start with something simple: the ability to upload files with a description. In htdocs/docuvault, create AE_File.php* with the following content.

*NOTE: Your application elements must have class names starting with AE_ for Application Element.

class AE_File extends SC_Element { static $ReturnBtnMsg, $CancelBtnMsg, $SearchBtnMsg, $SearchMsg, $CreateBtnMsg, $CreatedMsg, $CreateMsg, $CreateError, $UpdateBtnMsg, $UpdatedMsg, $UpdateMsg, $UpdateError, $DeleteBtnMsg, $DeletedMsg, $DeleteMsg, $DeleteError; function construct() { } }

Now we will add the DB key and the file and description fields in function construct().

function construct() { $this->id = new SD_AutoIncrementId(); $this->file = new SD_File('./files','File'); // 'Archivo' translated to 'File' $this->description = new SD_Text('Description'); // 'Descripción' translated to 'Description' }

Go to http://localhost/docuvault/ in your browser, you should see something like this: Tutorial Image 1

(images obtained with the browser in dark mode)

This is the file management screen:

it consists of a search bar and a list of existing files. Click on the icon to add an element. Tutorial Image 11

You should see the file creation form.

Tutorial Image 3

If you select a file, enter the description, and click create, you should see something like this:

Tutorial Image 2

To add fields to the search, we must modify the Element Data flags by adding an S to the fields we want to be searchable.

class AE_File extends SC_Element { … .. . function construct() { $this->id = new SD_AutoIncrementId(); $this->file = new SD_File('./files','File','S'); // 'Archivo' translated to 'File' $this->description = new SD_Text('Description','S'); // 'Descripción' translated to 'Description' $this->type = new SD_ElementContainer(new AE_FileType(),'Type','S'); // 'Tipo' translated to 'Type' } }

Upon refreshing, we should see something like this:

Tutorial Image 5

This allows filtering the files in the list by filling in the fields and using the search button.

We now have a system that uploads files with their descriptions and allows filtering them.

Using the actions, you can also "view", edit, and delete them.

If you check the database at: http://localhost:8081/index.php, you will see that a docuvault database was created with an AE_File table containing id, description, and file columns, and it has one record (or more if you played around with the system).

This initial system is quite good, but still far from our requirements.

The system administrator must also be able to create different categories or file types (contracts, deeds, invoices, etc.).

Let's create the AE_FileType element.

Create the file htdocs/docuvault/AE_FileType.php

<?php class AE_FileType extends SC_Element { static $ReturnBtnMsg, $CancelBtnMsg, $SearchBtnMsg, $SearchMsg, $CreateBtnMsg, $CreatedMsg, $CreateMsg, $CreateError, $UpdateBtnMsg, $UpdatedMsg, $UpdateMsg, $UpdateError, $DeleteBtnMsg, $DeletedMsg, $DeleteMsg, $DeleteError; function construct() { $this->id = new SD_AutoIncrementId(); $this->nombre = new SD_String('Name','SE'); // 'Nombre' translated to 'Name' } }

In your browser, open http://localhost/docuvault/AE_FileType/!showAdmin

And you will see the file type administrator. Create the types 'contract' and 'invoice'. You should have something like this:

Tutorial Image 6

File types are of little use if they are not used to categorize files. Let's add the file type to the files. In AE_File construct add: $this->type = new SD_ElementContainer(new AE_FileType(),'Type');

// 'Tipo' translated to 'Type'

It should look like this:

class AE_File extends SC_Element { … .. . function construct() { $this->id = new SD_AutoIncrementId(); $this->file = new SD_File('./files','File','S'); // 'Archivo' translated to 'File' $this->description = new SD_Text('Description','S'); // 'Descripción' translated to 'Description' $this->type = new SD_ElementContainer(new AE_FileType(),'Type'); // 'Tipo' translated to 'Type' } }

Back at http://localhost/docuvault/AE_File/!showAdmin, you should see:

Tutorial Image 7

The Warning text appears only once and indicates that the structure of the AE_File table has been updated.

When creating/uploading a new file, we will see a new field. The icon Tutorial Image 9 allows us to select an existing file type, while with

Tutorial Image 8

Tutorial Image 10 we can create a new file type.

Changing Original Functions

We now have a system that allows uploading, categorizing, and listing files. Let's add the ability to view them.

For this, we will create a new version of SD_File.php with different behavior when displaying the data, by rewriting the viewVal method.

To do this, create the Datas directory in the project folder and the file htdocs/docuvault/Datas/AD_File.php * with the following content.

*NOTE: Your application data classes must have names starting with AD_ for Application Data, and be located in the Datas subdirectory.

<?php class AD_File extends SD_File { public function viewVal(){ return new SI_Link($this->val(), $this->fileName()); } }

Additionally, we need to change the data type in our element. From SD_File to AD_File.

In the line $this->file = new SD_File('./files','File','S');

// 'Archivo' translated to 'File'

The S in SD_File indicates to the framework that it is a Simplon file, while the A in AD_File indicates it is specific to the application.

Users and Permissions

This system is of little use without user control and permissions;

to define these permissions, we will use the user and role classes.

We will start by modifying the last two lines of index.php, they should say:

'PERMISSIONS' => 'AE_User', //'' 'LOAD_ROLE_CLASS' => true,

This tells Simplon that we want to use subclasses of the 'AE_User' class, which in turn will inherit from SE_User, to control access, and that these subclasses correspond to subclasses named the same as the roles.

Let's review SE_User->contruct:

static::$cantAccessMsg = SC_Main::L('You can\'t access this page');

Defines the message users will see when trying to access a page for which they do not have permission.

self::$permissions = array( 'admin' => array('*'=>'allow'), 'user' => array( 'Admin'=>'deny', 'View'=>array( 'updateAction'=>'viewableWhen_id_=_CurrentUserId', // Typo corrected: 'viwableWhen' -> 'viewableWhen' 'createAction'=>'hide', 'deleteAction'=>'hide', ), 'Search'=>'deny', 'Update'=>array( 'id'=>'fixed_CurrentUserId', 'userRole'=>'fixed_CurrentUserRole', 'userName'=>'fixed_CurrentUserName', ), 'Create'=>'deny', 'Delete'=>'deny', '*'=>'deny', ), );

Defines which roles can view and modify users.

Users with the 'admin' role can do everything array('*'=>'allow'),.

Users with the 'user' role cannot administer 'Admin'=>'deny', search 'Search'=>'deny', create 'Create'=>'deny', or delete 'Delete'=>'deny'; they will not see links to create or delete.

Furthermore, they can only edit when the user ID to be edited matches their own ID, meaning they can only edit their own data.

'View'=>array( 'updateAction'=>'viewableWhen_id_=_CurrentUserId', // Typo corrected: 'viwableWhen' -> 'viewableWhen' 'createAction'=>'hide', 'deleteAction'=>'hide', ).

When editing, they cannot change their ID, role, or username.

'Update'=>array( 'id'=>'fixed_CurrentUserId', 'userRole'=>'fixed_CurrentUserRole', 'userName'=>'fixed_CurrentUserName', ) .

And anything else is forbidden.

'*'=>'deny' .

Finally, we see it has id, username, password, full name, role, and an empty menu.

$this->id = new SD_AutoIncrementId(null);

$this->userName = new SD_String(SC_Main::L('User'),'VCUSlRe');

$this->password = new SD_Password(SC_Main::L('Password'));

$this->fullName = new SD_String(SC_Main::L('Full name'),'SL');

$role = new SE_Role();

$this->userRole = new SD_ElementContainer($role,SC_Main::L('Role'),null,'RSL');

$this->userRole->layout(new SI_Select());

$this->menu = new SI_SystemMenu([]);

We also see

$this->sourceURL = new SD_Hidden(null,'vcuslerf',$_SERVER['REQUEST_URI']);

Which is used to return to the page from which the login process originated and

$this->userRole->layout(new SI_Select());

which changes the role selection in forms to a dropdown menu.

On this basis, which already has methods for authentication and checking user and element permissions, we will build our permission system for the app with two roles: users AE_User.php and administrators AE_Admin.php.

We will also create the AE_EmptyAdmin class, which automatically activates when there is no administrator user in the database.

htdocs/docuvault/AE_EmptyAdmin.php

<?php class AE_EmptyAdmin extends SE_EmptyAdmin { }

htdocs/docuvault/AE_User.php

<?php class AE_User extends SE_User { protected static $RoleID;

protected $defaultObject = 'AE_File', $defaultMethod = 'showAdmin';

static $menu, $ReturnBtnMsg, $CancelBtnMsg, $SearchBtnMsg, $SearchMsg, $CreateBtnMsg, $CreatedMsg, $CreateMsg, $CreateError, $UpdateBtnMsg, $UpdatedMsg, $UpdateMsg, $UpdateError, $DeleteBtnMsg, $DeletedMsg, $DeleteMsg, $DeleteError;

function construct($id = NULL, $storage = 'AE_User' ) { parent::construct($id, $storage);

//We need to ensure there is an user role in the DB and set it as a fixed value for this class if(!self::$RoleID){ $role = new SE_Role();

$role->roleName('user');

$search = new SE_Search(array('SE_Role')); $result = $search->getResults($role->toArray(), ['id'], 0, 1)[0]; if ($result && $result->id()) { self::$RoleID = $result->id();

} else { self::$RoleID = $role->create(); } }

@$Links[] = new SI_Link(SC_Main::$RENDERER->action('AE_File','showAdmin'), SC_MAIN::L('Files')); $Links[] = new SI_Link(SC_Main::$RENDERER->action($this,'showupdate'), SC_MAIN::L('My profile'));

$Links[] = new SI_AjaxLink(SC_Main::$RENDERER->action($this->getClass(),'logout'), SC_MAIN::L('Logout'), 'logout.svg');

self::$menu = new SI_SystemMenu($Links); } }

htdocs/docuvault/AE_Admin.php

<?php class AE_Admin extends AE_User { protected static $AdminRoleID;

static $menu, $ReturnBtnMsg, $CancelBtnMsg, $SearchBtnMsg, $SearchMsg, $CreateBtnMsg, $CreatedMsg, $CreateMsg, $CreateError, $UpdateBtnMsg, $UpdatedMsg, $UpdateMsg, $UpdateError, $DeleteBtnMsg, $DeletedMsg, $DeleteMsg, $DeleteError;

function construct($id = NULL, $storage = 'AE_User' ) { parent::construct($id, $storage); $this->storage('AE_User');

//We need to ensure there is an Admin role in the DB and set it as a fixed value for this class if(!self::$AdminRoleID){ $role = new SE_Role();

$role->roleName('admin');

$search = new SE_Search(array('SE_Role')); $result = $search->getResults($role->toArray(), ['id'], 0, 1)[0]; if ($result && $result->id()) { self::$AdminRoleID = $result->id();

} else { self::$AdminRoleID = $role->create(); } }

@$Links[] = new SI_Link(SC_Main::$RENDERER->action('AE_File','showAdmin'), SC_MAIN::L('Files')); $Links[] = new SI_Link(SC_Main::$RENDERER->action('AE_FileType','showAdmin'), SC_MAIN::L('File Types'));

// 'Files types' -> 'File Types'

$Links[] = new SI_Link(SC_Main::$RENDERER->action('AE_User','showAdmin'), SC_MAIN::L('Users'));

$Links[] = new SI_Link(SC_Main::$RENDERER->action('SE_Role','showAdmin'), SC_MAIN::L('Roles')); $Links[] = new SI_AjaxLink(SC_Main::$RENDERER->action($this->getClass(),'logout'), SC_MAIN::L('Logout'), 'logout.svg');

self::$menu = new SI_SystemMenu($Links); } }

Let's analyze these files: EmptyAdmin simply renames the existing Simplon class as an application class, which activates when no administrator is registered in the database.

AE_User and AE_Admin have:

With these three files, upon re-entering http://localhost/docuvault/, you should see the user creation screen and be able to create an administrator.

After creating it, log out and log in now with your new administrator.

You should see something like this:

Tutorial Image 11

And the link "Click here to go to your home page" will take you to the same screen.

If you use the top menu, you will see that you can access users and roles, but not Files or File Types.

This is because the initial screen AE_File->showAdmin and neither the files nor the file types have defined permissions.

To define them, we need to define the $permissions array like the one we saw in the SE_User class.

Let's add this declaration to AE_File.php:

static $permissions = array( 'admin' => array('*'=>'allow'), 'user' => array( ''=>'accessibleWhen_CurrentUserId_=_user', 'View'=>array( ''=>'accessibleWhen_CurrentUserId_=_user', 'user'=>'fixed_CurrentUserId', ), 'Admin'=>array( 'user'=>'fixed_CurrentUserId', 'updateAction'=>'viewableWhen_user_=_CurrentUserId', // Typo corrected: 'viwableWhen' -> 'viewableWhen' 'deleteAction'=>'hide', ), 'Search'=>array( ''=>'accessibleWhen_CurrentUserId_=_user', 'user'=>'fixed_CurrentUserId', ), 'Update'=>array( ''=>'accessibleWhen_CurrentUserId_=_user', 'user'=>'fixed_CurrentUserId', ), 'Create'=>array( 'user'=>'fixed_CurrentUserId', ), 'Delete'=>array( ''=>'accessibleWhen_CurrentUserId_=_user', 'user'=>'fixed_CurrentUserId', ), ), '*' => array('*'=>'deny') );

This tells the system:

That the administrator can do everything: 'admin' => array('*'=>'allow'),

The user can only view file information when the ID of the user who created the file matches (i.e., they can only view and manipulate their own files).

''=>'accessibleWhen_CurrentUserId_=_user',

''=>'accessibleWhen_CurrentUserId_=_user', indicates that methods related to search ('Search'), update ('Update'), and delete ('Delete') are only accessible for the user's own files.

Creation doesn't need it because it doesn't have an ID yet.

'user'=>'fixed_CurrentUserId', indicates that methods related to search ('Search'), update ('Update'), creation ('Create'), and deletion ('Delete') must have the user ID value fixed to that of the active user.

If a new method is created, it can be included directly in the permissions as another key, for example:

'NewMethod'=>array( // 'NuevoMetodo' translated to 'NewMethod' ''=>'accessibleWhen_CurrentUserId_=_user', 'user'=>'fixed_CurrentUserId', ),

In addition to permissions, we need to add the user to our files.

In the constructor, we will add:

$this->user = new SD_ElementContainer(new SE_User(),'User',null,'SL');

// 'Usuario' translated to 'User'

$this->user->layout(new SI_RadioButton());

And to change how the file type selection is presented, we will add:

$this->type->layout(new SI_Select());

In AE_FileType.php the permissions are simpler:

static $permissions = array( 'admin' => array('*'=>'allow'), '*' => array('*'=>'deny') );

And indicate that only the administrator can view, create, modify, and delete file types.

And we will modify the assignment of the user value by adding the user method to AE_File so that it modifies the path of file.

This will allow files to be stored in a subdirectory corresponding to the user.

function user($val = null){ if($val){ $this->__call('user',[$val]); $userDir=sanitizeFileName($this->Ouser()->viewVal()); $this->file->path('./files/'.$userDir); //$this->downloadPath('./d/files/'.$userDir); }else{ return $this->__call('user',null); } }

Interface Generation

Finally, we will make the administrator's initial screen one where they can choose, without going to the menu, between managing users, roles, files, and file types.

For this, we will change the initial method for administrator users to a custom screen.

To do this, in AE_Admin we will set the initial class and method for administrators.

protected $defaultClass = 'AE_Admin', $defaultMethod = 'startScreen';

And we will create the corresponding method that will return a screen with four icons linking to the different tasks.

function startScreen(){ $content = new SI_VContainer(); $titleText = SC_Main::L('Welcome'); $title = new SI_Title($titleText,5,'c'); $content->addItem($title); $buttons = new SI_HContainer();

$buttons->addItem(new SI_Link(SC_Main::$RENDERER->action('AE_File','showAdmin'), new SI_Image('Files.svg','Files','500rem','500rem')));

$buttons->addItem(new SI_Link(SC_Main::$RENDERER->action('AE_FileType','showAdmin'), new SI_Image('FileTypes.svg','File Types','500rem','500rem'))); $buttons->addItem(new SI_Link(SC_Main::$RENDERER->action('AE_User','showAdmin'), new SI_Image('Users.svg','Users','500rem','500rem'))); $buttons->addItem(new SI_Link(SC_Main::$RENDERER->action('SE_Role','showAdmin'), new SI_Image('Roles.svg','Roles','500rem','500rem'))); $content->addItem($buttons);

$page = new SI_systemScreen( $content,$titleText );

return $page; }

This method uses various Simplon interface objects, whose classes are located in the \simplon-php\Renderers\htmlJQuery directory and start with the SI_ prefix. Notable for their frequent use are: SI_VContainer, SI_HContainer, for arranging elements vertically and horizontally, and SI_systemScreen for generating the system screen.

With this, we fulfill the project specifications:

“DocuVault is a very simple demo of a document management system to facilitate work between legal or auditing firms and their clients. The company administrator can define new users and file types (contracts, deeds, invoices, etc.). Clients can upload their files and categorize them. The company can view files from all clients; clients can only view their own files.”

Refinement

However, there are still some things to refine, for example: