0011412: A mobile client for Tine 2.0
authorFlávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
Thu, 5 Nov 2015 13:14:04 +0000 (11:14 -0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 18 Nov 2015 11:44:54 +0000 (12:44 +0100)
- Simplified interface to Tine 2.0 applications
- Accessible for visual impaired people
- Current version allow to access E-mail and Addressbook
- How-to available in https://forge.tine20.org/view.php?id=11412

Change-Id: I4f3523f273794f190b56506f3180117e66e2c30a
Reviewed-on: https://gerrit.tine20.org/tine20/3251
Tested-by: jenkins user
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
208 files changed:
clients/ExpressoLite/build/README [new file with mode: 0644]
clients/ExpressoLite/build/build.php [new file with mode: 0644]
clients/ExpressoLite/build/build_cordova.sh [new file with mode: 0755]
clients/ExpressoLite/build/cordova/.gitignore [new file with mode: 0644]
clients/ExpressoLite/build/cordova/README.md [new file with mode: 0644]
clients/ExpressoLite/build/cordova/config.xml [new file with mode: 0644]
clients/ExpressoLite/build/cordova/cordova-build-src/common-js/CordovaConfig.js [new file with mode: 0644]
clients/ExpressoLite/build/cordova/icons/expressobr-hdpi.png [new file with mode: 0644]
clients/ExpressoLite/build/cordova/icons/expressobr-ldpi.png [new file with mode: 0644]
clients/ExpressoLite/build/cordova/icons/expressobr-mdpi.png [new file with mode: 0644]
clients/ExpressoLite/build/cordova/icons/expressobr-xhdpi.png [new file with mode: 0644]
clients/ExpressoLite/build/cordova/icons/expressobr-xxhdpi.png [new file with mode: 0644]
clients/ExpressoLite/build/cordova/icons/expressobr-xxxhdpi.png [new file with mode: 0644]
clients/ExpressoLite/build/yuicompressor/LICENSE.TXT [new file with mode: 0644]
clients/ExpressoLite/build/yuicompressor/yuicompressor-2.4.8.jar [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Core/DateUtils.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Core/MessageIds.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Core/ShowFeedback.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Core/Template/ShowFeedbackTemplate.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Core/Template/ShowFeedbackTemplate.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Core/Template/general.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Dispatcher.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Handler.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Login/Login.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Login/Logoff.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Login/Main.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Login/Template/MainTemplate.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Login/Template/MainTemplate.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/ComposeMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/ConfirmMessageAction.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/DeleteMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Main.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/MarkMessageAsUnread.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/MoveMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/OpenFolder.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/OpenMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/ProcessMessageAction.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/SendMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/ComposeMessageTemplate.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/ComposeMessageTemplate.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/MainTemplate.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/MainTemplate.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenFolderTemplate.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenFolderTemplate.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenMessageTemplate.css [new file with mode: 0644]
clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenMessageTemplate.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/bootstrap.php [new file with mode: 0644]
clients/ExpressoLite/src/accessible/index.php [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetCatalogMenu.css [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetCatalogMenu.js [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetContactDetails.css [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetContactDetails.html [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetContactDetails.js [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetContactList.css [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetContactList.html [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetContactList.js [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetLetterIndex.css [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/WidgetLetterIndex.js [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/addressbook.css [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/addressbook.js [new file with mode: 0644]
clients/ExpressoLite/src/addressbook/index.html [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/AjaxProcessor.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/LiteRequestProcessor.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/ChangeExpiredPassword.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/CheckSessionStatus.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/DeleteMessages.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/DownloadAttachment.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/EchoParams.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetAllRegistryData.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContact.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContactCatalogsCategories.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContactsByFilter.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetPersonalContacts.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/JoinTempFiles.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/LiteRequest.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Login.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Logoff.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MarkAsHighlighted.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MarkAsRead.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MoveMessages.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SaveMessage.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SaveMessageDraft.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchContactsByEmail.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchContactsByToken.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchEvents.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchFolders.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchHeadlines.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SetEventConfirmation.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/UpdateMessageCache.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/UploadTempFile.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Utils/MessageUtils.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Backend/TineSessionRepository.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/CaptchaRequiredException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/LiteException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/NoTineSessionException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/RpcException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/TineErrorException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/TineSessionExpiredException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/Exception/UserMismatchException.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/TineTunnel/CookieHandler.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/TineTunnel/JsonRpc.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/TineTunnel/Request.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/TineTunnel/TineJsonRpc.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ExpressoLite/TineTunnel/TineSession.php [new file with mode: 0644]
clients/ExpressoLite/src/api/SplClassLoader.php [new file with mode: 0644]
clients/ExpressoLite/src/api/ajax.php [new file with mode: 0644]
clients/ExpressoLite/src/api/bootstrap.php [new file with mode: 0644]
clients/ExpressoLite/src/calendar/DateCalc.js [new file with mode: 0644]
clients/ExpressoLite/src/calendar/Events.js [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetEvents.css [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetEvents.html [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetEvents.js [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetMonth.css [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetMonth.html [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetMonth.js [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetWeek.css [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetWeek.html [new file with mode: 0644]
clients/ExpressoLite/src/calendar/WidgetWeek.js [new file with mode: 0644]
clients/ExpressoLite/src/calendar/calendar.css [new file with mode: 0644]
clients/ExpressoLite/src/calendar/calendar.js [new file with mode: 0644]
clients/ExpressoLite/src/calendar/index.html [new file with mode: 0644]
clients/ExpressoLite/src/common-js/App.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Contacts.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/ContactsAutocomplete.css [new file with mode: 0644]
clients/ExpressoLite/src/common-js/ContactsAutocomplete.html [new file with mode: 0644]
clients/ExpressoLite/src/common-js/ContactsAutocomplete.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/ContextMenu.css [new file with mode: 0644]
clients/ExpressoLite/src/common-js/ContextMenu.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Cordova.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/CordovaConfig.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/DateFormat.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Dialog.css [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Dialog.html [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Dialog.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Layout.css [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Layout.html [new file with mode: 0644]
clients/ExpressoLite/src/common-js/Layout.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/TextBadges.css [new file with mode: 0644]
clients/ExpressoLite/src/common-js/TextBadges.html [new file with mode: 0644]
clients/ExpressoLite/src/common-js/TextBadges.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/UploadFile.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/UrlStack.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/general.css [new file with mode: 0644]
clients/ExpressoLite/src/common-js/index.php [new file with mode: 0644]
clients/ExpressoLite/src/common-js/jquery.min.js [new file with mode: 0644]
clients/ExpressoLite/src/common-js/require.min.js [new file with mode: 0644]
clients/ExpressoLite/src/conf.php [new file with mode: 0644]
clients/ExpressoLite/src/cordova.js [new file with mode: 0644]
clients/ExpressoLite/src/img/48px_error.png [new file with mode: 0644]
clients/ExpressoLite/src/img/72px_Alert.png [new file with mode: 0644]
clients/ExpressoLite/src/img/72px_confirm.png [new file with mode: 0644]
clients/ExpressoLite/src/img/72px_ok.png [new file with mode: 0644]
clients/ExpressoLite/src/img/arrows.png [new file with mode: 0644]
clients/ExpressoLite/src/img/attendee-status.png [new file with mode: 0644]
clients/ExpressoLite/src/img/bg-acess-key.png [new file with mode: 0644]
clients/ExpressoLite/src/img/check0.png [new file with mode: 0644]
clients/ExpressoLite/src/img/check1.png [new file with mode: 0644]
clients/ExpressoLite/src/img/chromiumthrobber.svg [new file with mode: 0644]
clients/ExpressoLite/src/img/e_mail.png [new file with mode: 0644]
clients/ExpressoLite/src/img/favicon.png [new file with mode: 0644]
clients/ExpressoLite/src/img/flags.png [new file with mode: 0644]
clients/ExpressoLite/src/img/fondo.jpg [new file with mode: 0644]
clients/ExpressoLite/src/img/index.php [new file with mode: 0644]
clients/ExpressoLite/src/img/layout-icons32.png [new file with mode: 0644]
clients/ExpressoLite/src/img/logo-expressobr-bottom.png [new file with mode: 0644]
clients/ExpressoLite/src/img/logo-expressobr-internal.png [new file with mode: 0644]
clients/ExpressoLite/src/img/logo-expressobr-top.png [new file with mode: 0644]
clients/ExpressoLite/src/img/mobile.png [new file with mode: 0644]
clients/ExpressoLite/src/img/page_ear.png [new file with mode: 0644]
clients/ExpressoLite/src/img/page_ear_selected.png [new file with mode: 0644]
clients/ExpressoLite/src/img/person-generic.gif [new file with mode: 0644]
clients/ExpressoLite/src/img/person-gmail.png [new file with mode: 0644]
clients/ExpressoLite/src/img/person-govbr.png [new file with mode: 0644]
clients/ExpressoLite/src/img/person-outlook.png [new file with mode: 0644]
clients/ExpressoLite/src/img/person-yahoo.png [new file with mode: 0644]
clients/ExpressoLite/src/img/person-zabbix.png [new file with mode: 0644]
clients/ExpressoLite/src/img/phone.png [new file with mode: 0644]
clients/ExpressoLite/src/img/store-apple.png [new file with mode: 0644]
clients/ExpressoLite/src/img/store-play.png [new file with mode: 0644]
clients/ExpressoLite/src/img/universal-access.png [new file with mode: 0644]
clients/ExpressoLite/src/index.html [new file with mode: 0644]
clients/ExpressoLite/src/login.css [new file with mode: 0644]
clients/ExpressoLite/src/login.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/Contacts.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/ThreadMail.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetAttacher.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetAttacher.html [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetAttacher.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetCompose.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetCompose.html [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetCompose.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetFolders.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetFolders.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetHeadlines.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetHeadlines.html [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetHeadlines.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetMessages.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetMessages.html [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetMessages.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetSearchAddr.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetSearchAddr.html [new file with mode: 0644]
clients/ExpressoLite/src/mail/WidgetSearchAddr.js [new file with mode: 0644]
clients/ExpressoLite/src/mail/index.html [new file with mode: 0644]
clients/ExpressoLite/src/mail/mail.css [new file with mode: 0644]
clients/ExpressoLite/src/mail/mail.js [new file with mode: 0644]
clients/ExpressoLite/src/version.php [new file with mode: 0644]
clients/ExpressoLite/tests/README [new file with mode: 0644]

diff --git a/clients/ExpressoLite/build/README b/clients/ExpressoLite/build/README
new file mode 100644 (file)
index 0000000..eb04c0b
--- /dev/null
@@ -0,0 +1 @@
+Here are the build tools to create a package of the ExpressoLite
diff --git a/clients/ExpressoLite/build/build.php b/clients/ExpressoLite/build/build.php
new file mode 100644 (file)
index 0000000..0e307a1
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+
+if(!isset($argv[1]) || !isset($argv[2]) || !isset($argv[3])) {
+    die("\n".
+        " Minifier for Expresso Lite\n".
+        " Usage:\n".
+        "   php build.php source_folder dest_folder version_name\n\n");
+}
+
+
+$src = $argv[1];
+$dest = $argv[2];
+$versionName = $argv[3];
+
+//$src = '/var/www/git/Expressov3/scripts/expressolite';
+//$dest = '/var/www/teste5';
+
+function Now() {
+    $tod = gettimeofday();
+    return $tod['sec'] * 1000 + round($tod['usec'] / 1000); // timestamp in milliseconds
+}
+function EndsWith($str, $what) {
+    return substr($str, -strlen($what)) === $what;
+}
+
+$totalDirs = 0;
+$totalFiles = 0;
+$totalYui = 0;
+
+function ProcessDir($dir) {
+    global $src, $dest, $totalDirs, $totalFiles, $totalYui;
+
+    if(!file_exists($dest.substr($dir, strlen($src)))) {
+        echo 'mkdir -> '.$dest.substr($dir, strlen($src))."\n";
+        mkdir($dest.substr($dir, strlen($src)));
+    }
+
+    $dir .= EndsWith($dir, '/') ? '*' : '/*';
+    foreach(glob($dir) as $file) {
+        if(is_dir($file)) {
+            if(EndsWith($file, '_build')) continue;
+            ++$totalDirs;
+            ProcessDir($file);
+        } else {
+            if(EndsWith($file, '.zScript build ip') || EndsWith($file, '.gz') || EndsWith($file, '.bz2')) {
+                continue;
+            } else if( (EndsWith($file, '.js') && !EndsWith($file, '.min.js')) || EndsWith($file, '.css') ) {
+                echo 'YUI Compressor -> '.$dest.substr($file, strlen($src))."\n";
+                system('java -jar yuicompressor/yuicompressor-2.4.8.jar -o '.$dest.substr($file, strlen($src)).' '.$file);
+                ++$totalYui;
+            } else {
+                copy($file, $dest.substr($file, strlen($src)));
+            }
+            ++$totalFiles;
+        }
+    }
+}
+
+function ReplaceVersionName($fileName) {
+    global $versionName;
+    echo "Version name: $versionName\n";
+    $str=file_get_contents($fileName);
+
+    $oldValue = "define('PACKAGE_STRING', 'lite_development');";
+    $newValue = "define('PACKAGE_STRING', '$versionName');";
+
+    $str=str_replace($oldValue, $newValue,$str);
+
+    file_put_contents($fileName, $str);
+}
+
+
+$t0 = Now();
+if(!EndsWith($src, '/')) $src .= '/';
+if(!EndsWith($dest, '/')) $dest .= '/';
+ProcessDir($src);
+ReplaceVersionName($dest . 'version.php');
+$t = (Now() - $t0) / 1000;
+echo "Total: $totalFiles files ($totalYui minified) within $totalDirs directories in $t seconds.\n";
+
diff --git a/clients/ExpressoLite/build/build_cordova.sh b/clients/ExpressoLite/build/build_cordova.sh
new file mode 100755 (executable)
index 0000000..bf57083
--- /dev/null
@@ -0,0 +1,186 @@
+#!/bin/sh
+
+# Expresso Lite
+# Script that builds the cordova application. This script follows
+# these steps
+#
+# 1 - Checks if script usage is correct (script must be invoked
+#     with one parameter at most). See showusage () for more details
+# 2 - Sets variables to define relevant paths to the build process
+# 3 - Checks if the cordova app project dir exists. If it does not
+#     exist (or if -regenerate is informed), we call cordova to
+#     create a new project, overwriting config.xml with our own and
+#     adding the Android plarform
+# 4 - Clears cordova app 'www' folder to prepare it for our files
+# 5 - Copies Expresso Lite src folder contents to cordova app 'www'
+#     folder
+# 6 - Deletes files that are not needed in the cordova app (like api
+#     files, accessible module, and others)
+# 7 - Overwrites some specific files to make Expresso Lite behave
+#     correctly in cordova. These files are present cordova-build-src.
+#     Currently, only CordovaConfig.js should be overwritten
+# 8 - Calls Cordova specific build operations. See usage for more
+#     details
+#
+# @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+# @author    Charles Wust <charles.wust@serpro.gov.br>
+# @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+
+#Checks if cordova is installed
+command -v cordova -v >/dev/null 2>&1 || { echo >&2 "This script requires cordova to be installed. See ExpressoLite/cordova/README.md for more details."; exit 1; }
+
+
+BUILD="-build"
+EMULATE="-emulate"
+RUN="-run"
+NOBUILD="-nobuild"
+REGENERATE="-regenerate"
+
+ALLOWED_PARAMS="$BUILD $EMULATE $RUN $NOBUILD $REGENERATE"
+
+showusage ()
+{
+  echo ""
+  echo "Usage: ./build_cordova.sh [$BUILD|$EMULATE|$RUN|$NOBUILD|$REGENERATE] "
+  echo "  $BUILD: Will build the cordova project, generating an APK"
+  echo "  $EMULATE: Will start android emulation after the build"
+  echo "  $RUN: Will run the android app in any connected devices (if no device is found, it starts the emulator)"
+  echo "  $NOBUILD: Will only prepare the cordova project, but no cordova build or emulation will be invoked"
+  echo "  $REGENERATE: Will recreate cordova from scratch (applying new config.xml and icons). No cordova build or emulation will be invoked."
+  echo ""
+  echo "$BUILD will be used if no parameter is informed"
+}
+
+#Checking # of parameters ($# is the number of parameters)
+case $# in
+  0)
+    echo "No parameter informed, using default mode: $BUILD"
+    MODE=$BUILD
+    ;;
+  1)
+    #checking if parameter is one of the allowed
+    for PARAM in $ALLOWED_PARAMS
+    do
+      if [ "$1" = "$PARAM" ]
+      then
+        MODE=$1
+        break
+      fi
+    done
+    #if we get here without a mode, it means we have an invalid parameter
+    if [ -z $MODE ]
+    then
+      echo "Unkown parameter: $1"
+      showusage
+      exit
+    fi
+    ;;
+  *)
+    echo "Too many parameters"
+    showusage
+    exit
+    ;;
+esac
+
+
+
+#setting relevant paths to the build process
+
+#Expresso Lite main dir
+PROJECT_DIR=${PWD}/..
+
+#Cordova base dir
+CORDOVA_MAIN_DIR=${PWD}/cordova
+
+#The name of the folder that will store the cordova app project
+CORDOVA_APP_DIR_NAME=cordova_app
+
+#Cordova App project full path (derived from previous variables)
+CORDOVA_APP_DIR=$CORDOVA_MAIN_DIR/$CORDOVA_APP_DIR_NAME
+
+#Folder that stores Expresso Lite src files
+SRC_DIR=$PROJECT_DIR/src
+
+#Package name used by cordova
+CORDOVA_PACKAGE_NAME=br.gov.serpro.expressobr
+
+#Name of the Cordova App. This is the name shown on the mobile devices
+CORDOVA_APP_NAME=ExpressoBr
+
+
+
+
+#Delete the cordova app dir to force its regenaration, it that is the case
+if [ "$MODE" = "$REGENERATE" ]
+then
+  echo "Cleaning $CORDOVA_APP_DIR to regenerate project from scratch"
+  rm -rf $CORDOVA_APP_DIR
+fi
+
+
+
+#If cordova app project dir is not available, make cordova create the project
+if [ ! -d "$CORDOVA_APP_DIR" ]
+then
+  echo "Creating the Cordova App project"
+  cd $CORDOVA_MAIN_DIR
+  cordova create $CORDOVA_APP_DIR_NAME $CORDOVA_PACKAGE_NAME $CORDOVA_APP_NAME
+  echo "Overwriting config.xml and adding icons"
+  cp $CORDOVA_MAIN_DIR/config.xml $CORDOVA_APP_DIR
+  cp -r $CORDOVA_MAIN_DIR/icons $CORDOVA_APP_DIR
+  echo "Adding Android platform"
+  cd $CORDOVA_APP_DIR
+  cordova platform add android
+fi
+
+
+#Lets copy Expresso Lite to Cordova's www folder
+echo "Cleaning www folder..."
+rm -rf $CORDOVA_APP_DIR/www/*
+
+echo "Copying Expresso Lite src folder to cordova project..."
+cp -r $SRC_DIR/* $CORDOVA_APP_DIR/www
+
+
+#the following files and folders shoudn't be in the cordova version
+ITEMS_TO_REMOVE="conf.php version.php accessible api"
+
+echo "Removing unwanted files and folders from cordova project..."
+for ITEM in $ITEMS_TO_REMOVE
+do
+ ITEM_PATH=$CORDOVA_APP_DIR/www/$ITEM
+ rm -rf $ITEM_PATH
+done
+
+
+#Overwrite what needs to be overwritten in the cordova project
+echo "Overwriting cordova project specific files..."
+cp -rf $CORDOVA_MAIN_DIR/cordova-build-src/* $CORDOVA_APP_DIR/www
+
+
+#All done, let's call cordova to do what the user asked for
+cd $CORDOVA_APP_DIR
+case $MODE in
+  "$BUILD")
+    echo "Building cordova project..."
+    cordova build android
+    ;;
+  "$EMULATE")
+    echo "Emulating cordova project..."
+    cordova emulate android
+    ;;
+  "$RUN")
+    echo "Running cordova project..."
+    cordova run android
+    ;;
+  "$NOBUILD")
+    echo "-nobuild parameter informed, cordova build will be skipped."
+    ;;
+  "$REGENERATE")
+    echo "$REGENERATE parameter was informed, project was recreated."
+    ;;
+  *)
+    echo "Problems with this script, please contact developers"
+    ;;
+esac
+
diff --git a/clients/ExpressoLite/build/cordova/.gitignore b/clients/ExpressoLite/build/cordova/.gitignore
new file mode 100644 (file)
index 0000000..abb44c9
--- /dev/null
@@ -0,0 +1 @@
+cordova_app
diff --git a/clients/ExpressoLite/build/cordova/README.md b/clients/ExpressoLite/build/cordova/README.md
new file mode 100644 (file)
index 0000000..89f7b3f
--- /dev/null
@@ -0,0 +1,62 @@
+EXPRESSO LITE CORDOVA APP
+
+This folder contains the files needed to build ExpressoLite Cordova Application. They are basically:
+  - config.xml: holds cordova configuration to build ExpressoLite app
+  - icons: self explanatory
+  - cordova_app: contains the cordova project itself. This will be generated by the build script
+
+To build the cordova app, please use build_cordova.sh. It is located in the 'build' folder.
+
+
+
+PRE-REQUISITES:
+
+Keep in mind that in order to build the cordova project you need to have the following tools installed on your computer:
+  - Node.js (needed to install cordova)
+  - Cordova
+  - Java (needed by the Android SDK)
+  - Android SDK
+  - In some environments, Ant is also required (to install it use 'sudo apt-get install ant')
+
+
+
+INSTALLING NODEJS AND CORDOVA
+
+Before you can install Cordova, first you'll need to install Node.js. There are several ways to install it, but this seems to be the easiest way:
+
+------------
+#extracted from https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager
+sudo apt-get install curl
+curl --silent --location https://deb.nodesource.com/setup_0.12 | sudo bash -
+sudo apt-get install nodejs
+------------
+
+Once NodeJs is installed, installing Cordova is easy:
+
+------------
+sudo npm install -g cordova
+------------
+
+
+INSTALLING THE ANDROID SDK
+
+Android SDK is needed to build the Android App with Cordova. To make it available, follow these steps:
+
+1 - Make sure you have java JDK installed
+2 - Download Android SDK from https://developer.android.com/sdk/index.html#Other
+3 - Extract it to a folder of your preference (i.e: /home/user/android)
+4 - Add android/tools and android/platform-tools to your path. You may do this by adding the following line to your ~/.profile file:  accordingly)
+---------
+export PATH="$HOME/android/tools:$HOME/android/platform-tools:$PATH"
+---------
+5 - You may need to restart your linux user session for the changes to take effect. Run 'android -help' to check if it is installed correctly
+6 - Run 'android sdk' to open the SDK Manager
+7 - Wait for the tool to download the list of tools. Once the list is loaded, select the following options (DO NOT SELECT EVERYTHING ON THE LIST, it takes a VERY long time to download):
+  - Android SDK Tools (the latest)
+  - Android SDK Platform-tools (the latest)
+  - Android SDK Build-tools (the latest)
+  - Android 5.1.1 (API 22)
+8 - Click in the button named 'Install 11 packages...' (on the bottom right corner)
+9 - Accept all license terms (you may have to select them one by one)
+10 - Wait for the download (you may go for a coffee, it takes quite a while)
+
diff --git a/clients/ExpressoLite/build/cordova/config.xml b/clients/ExpressoLite/build/cordova/config.xml
new file mode 100644 (file)
index 0000000..16d68b6
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget id="br.gov.serpro.expressobr" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
+    <name>ExpressoBr</name>
+    <description>
+        Cliente de e-mail para o ExpressoBr em dispositivos móveis.
+    </description>
+    <author email="expresso@serpro.gov.br" href="http://serpro.gov.br">
+        Equipe de desenvolvimento do ExpressoBr
+    </author>
+    <content src="index.html" />
+    <access origin="*" />
+    <platform name="android">
+        <icon src="icons/expressobr-ldpi.png" density="ldpi" />
+        <icon src="icons/expressobr-mdpi.png" density="mdpi" />
+        <icon src="icons/expressobr-hdpi.png" density="hdpi" />
+        <icon src="icons/expressobr-xhdpi.png" density="xhdpi" />
+    </platform>
+</widget>
diff --git a/clients/ExpressoLite/build/cordova/cordova-build-src/common-js/CordovaConfig.js b/clients/ExpressoLite/build/cordova/cordova-build-src/common-js/CordovaConfig.js
new file mode 100644 (file)
index 0000000..5ea3fb8
--- /dev/null
@@ -0,0 +1,18 @@
+/*!
+ * Expresso Lite
+ * File containing Cordova relevant information.
+ * This is the version used in the phone application, and replaces
+ * the default version used in the server environment.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+define({
+    isEnabled: true,
+    liteBackendUrl: 'https://m.expresso.serpro.gov.br'
+    // This should be the address where Expresso Lite is
+    // available (without trailing '/' )
+});
diff --git a/clients/ExpressoLite/build/cordova/icons/expressobr-hdpi.png b/clients/ExpressoLite/build/cordova/icons/expressobr-hdpi.png
new file mode 100644 (file)
index 0000000..8ec03db
Binary files /dev/null and b/clients/ExpressoLite/build/cordova/icons/expressobr-hdpi.png differ
diff --git a/clients/ExpressoLite/build/cordova/icons/expressobr-ldpi.png b/clients/ExpressoLite/build/cordova/icons/expressobr-ldpi.png
new file mode 100644 (file)
index 0000000..0138dbb
Binary files /dev/null and b/clients/ExpressoLite/build/cordova/icons/expressobr-ldpi.png differ
diff --git a/clients/ExpressoLite/build/cordova/icons/expressobr-mdpi.png b/clients/ExpressoLite/build/cordova/icons/expressobr-mdpi.png
new file mode 100644 (file)
index 0000000..794ab5c
Binary files /dev/null and b/clients/ExpressoLite/build/cordova/icons/expressobr-mdpi.png differ
diff --git a/clients/ExpressoLite/build/cordova/icons/expressobr-xhdpi.png b/clients/ExpressoLite/build/cordova/icons/expressobr-xhdpi.png
new file mode 100644 (file)
index 0000000..0fd56a0
Binary files /dev/null and b/clients/ExpressoLite/build/cordova/icons/expressobr-xhdpi.png differ
diff --git a/clients/ExpressoLite/build/cordova/icons/expressobr-xxhdpi.png b/clients/ExpressoLite/build/cordova/icons/expressobr-xxhdpi.png
new file mode 100644 (file)
index 0000000..14697ed
Binary files /dev/null and b/clients/ExpressoLite/build/cordova/icons/expressobr-xxhdpi.png differ
diff --git a/clients/ExpressoLite/build/cordova/icons/expressobr-xxxhdpi.png b/clients/ExpressoLite/build/cordova/icons/expressobr-xxxhdpi.png
new file mode 100644 (file)
index 0000000..9e9af6a
Binary files /dev/null and b/clients/ExpressoLite/build/cordova/icons/expressobr-xxxhdpi.png differ
diff --git a/clients/ExpressoLite/build/yuicompressor/LICENSE.TXT b/clients/ExpressoLite/build/yuicompressor/LICENSE.TXT
new file mode 100644 (file)
index 0000000..8a1c7a1
--- /dev/null
@@ -0,0 +1,54 @@
+YUI Compressor Copyright License Agreement (BSD License)
+
+Copyright (c) 2013, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This software also requires access to software from the following sources:
+
+The Jarg Library v 1.0 ( http://jargs.sourceforge.net/ ) is available
+under a BSD License � Copyright (c) 2001-2003 Steve Purcell,
+Copyright (c) 2002 Vidar Holen, Copyright (c) 2002 Michal Ceresna and
+Copyright (c) 2005 Ewan Mellor.
+
+The Rhino Library ( http://www.mozilla.org/rhino/ ) is dually available
+under an MPL 1.1/GPL 2.0 license, with portions subject to a BSD license.
+
+Additionally, this software contains modified versions of the following
+component files from the Rhino Library:
+
+[org/mozilla/javascript/Decompiler.java]
+[org/mozilla/javascript/Parser.java]
+[org/mozilla/javascript/Token.java]
+[org/mozilla/javascript/TokenStream.java]
+
+The modified versions of these files are distributed under the MPL v 1.1
+( http://www.mozilla.org/MPL/MPL-1.1.html )
diff --git a/clients/ExpressoLite/build/yuicompressor/yuicompressor-2.4.8.jar b/clients/ExpressoLite/build/yuicompressor/yuicompressor-2.4.8.jar
new file mode 100644 (file)
index 0000000..a1cf0a0
Binary files /dev/null and b/clients/ExpressoLite/build/yuicompressor/yuicompressor-2.4.8.jar differ
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Core/DateUtils.php b/clients/ExpressoLite/src/accessible/Accessible/Core/DateUtils.php
new file mode 100644 (file)
index 0000000..ed5e81d
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Date formatting routines.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Core;
+
+class DateUtils
+{
+    /**
+     * Formats any date needed for accessible.
+     *
+     * @param int $dateParam             timestamp int.
+     * @param boolean $setCompleteFormat specify a format.
+     * @return string                    formatted date.
+     */
+    public static function getFormattedDate($dateParam, $setCompleteFormat=false)
+    {
+        if(!isset($dateParam) || !is_int($dateParam )){
+            return '';
+        }
+
+        $timeReceived = date('H:i', $dateParam);
+        $dayReceived = date('d/m/Y', $dateParam);
+        $weekDayReceived = DateUtils::getWeekDay($dateParam);
+
+        if ($setCompleteFormat) {
+            return $weekDayReceived . ', ' . $dayReceived . ', ' . $timeReceived;
+        }
+
+        if ($dateParam >= strtotime('today 00:00')) {
+            return date('\\h\\o\\j\\e, H:i', $dateParam);
+        } elseif ($dateParam >= strtotime('yesterday 00:00')) {
+            return 'ontem, ' . $timeReceived;
+        } elseif ($dateParam >= strtotime('-6 day 00:00')) {
+            return $weekDayReceived . ', ' . $timeReceived;
+        } else {
+            return date('d/m/Y', $dateParam);
+        }
+    }
+
+    /**
+     * Gets translation of the day of the week.
+     *
+     * @param int $dateParam Date parameter.
+     * @return string        Translated day of the week.
+     */
+    private static function getWeekDay($dateParam)
+    {
+        $weekDay = date('w', $dateParam);
+        switch($weekDay) {
+            case'0': $weekDay = 'domingo'; break;
+            case'1': $weekDay = 'segunda'; break;
+            case'2': $weekDay = 'terça';   break;
+            case'3': $weekDay = 'quarta';  break;
+            case'4': $weekDay = 'quinta';  break;
+            case'5': $weekDay = 'sexta';   break;
+            case'6': $weekDay = 'sábado';  break;
+        }
+        return $weekDay;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Core/MessageIds.php b/clients/ExpressoLite/src/accessible/Accessible/Core/MessageIds.php
new file mode 100644 (file)
index 0000000..4b2f228
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Utility class that provides mail message facilities that are
+ * shared by several accessible request handlers.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Core;
+
+class MessageIds
+{
+    /**
+     * Receive the parameters and creates a string comma separated of message ids
+     *
+     * @param object $params Contains 'check_' attributes each representing a message id
+     * @return string Comma separated of message ids
+     */
+    public static function paramsToString($params)
+    {
+        $objVarToArray = get_object_vars($params);
+        foreach($objVarToArray as $key => $value) {
+            if (!preg_match('/check_/', $key)) {
+                unset($objVarToArray[$key]);
+            }
+        }
+
+        return implode(',', $objVarToArray);
+    }
+
+    /**
+     * Gets the number of messages id
+     *
+     * @param string $messageIds Not empty and has one or more message ids(comma separated)
+     * @return int A value indicating the number of message ids
+     */
+    public static function messageCount($messageIds)
+    {
+        return $messageIds === '' ? 0 : count(explode(',', $messageIds));
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Core/ShowFeedback.php b/clients/ExpressoLite/src/accessible/Accessible/Core/ShowFeedback.php
new file mode 100644 (file)
index 0000000..f57d1d9
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Controls the display of feedback and confirmation actions message.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Core;
+
+use Accessible\Handler;
+use Accessible\Dispatcher;
+use ExpressoLite\Backend\LiteRequestProcessor;
+
+class ShowFeedback extends Handler
+{
+    /**
+     * @var MSG_SUCCESS.
+     */
+    const MSG_SUCCESS = 'feedbackMessageSuccess';
+
+    /**
+     * @var MSG_ERROR.
+     */
+    const MSG_ERROR = 'feedbackMessageError';
+
+    /**
+     * @var MSG_ALERT.
+     */
+    const MSG_ALERT = 'feedbackMessageAlert';
+
+    /**
+     * @var MSG_CONFIRM.
+     */
+    const MSG_CONFIRM = 'feedbackMessageConfirm';
+
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        $this->showTemplate('ShowFeedbackTemplate', (object) array(
+            'typeMsg' => isset($params->typeMsg) ? $params->typeMsg : ShowFeedback::MSG_SUCCESS,
+            'message' =>  $params->message,
+            'destinationText' => $params->destinationText,
+            'destinationUrl' => $this->makeUrl($params->destinationUrl->action,
+                isset($params->destinationUrl->params) ? $params->destinationUrl->params : array()
+             ),
+            'buttons' => isset($params->buttons) ? $this->prepareButtonsForDisplay($params->buttons) : array()
+        ));
+    }
+
+    /**
+     * Prepare buttons data, such as the url and button text value, to be displayed
+     *
+     * @param object $buttonParams The object that contains the information of the buttons
+     * @return returns an array with prepared buttons configuration to be displayed
+     */
+    private function prepareButtonsForDisplay($buttonParams)
+    {
+        $buttons = array();
+        foreach ($buttonParams as $buttonParam) {
+            $buttons[] = (object) array(
+                'url' => $this->makeUrl($buttonParam->action, $buttonParam->params),
+                'value' => $buttonParam->params['confirmOption']
+            );
+        }
+        return $buttons;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Core/Template/ShowFeedbackTemplate.css b/clients/ExpressoLite/src/accessible/Accessible/Core/Template/ShowFeedbackTemplate.css
new file mode 100644 (file)
index 0000000..e5a5c3a
--- /dev/null
@@ -0,0 +1,36 @@
+/*!
+ * Expresso Lite
+ * CSS for ShowFeedbackTemplate.php.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Fatima Tonon <fatima.tonon@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+#feedback {
+    display:table;
+    margin:0 auto;
+    margin-top:50px;
+    padding:40px 50px 20px 50px;
+    border:2px solid #2C353E;
+    border-radius:10px;
+    background-repeat:no-repeat;
+    background-position:10px 40px;
+}
+
+#feedbackMessage {
+    display:block;
+    padding:0px 10px 50px 70px;
+    font-size:140%;
+    color:#2C353E;
+    font-weight:600;
+}
+
+#buttons { text-align:center;}
+#buttons ul { padding-top: 15px; }
+
+.feedbackMessageSuccess { background-image:url("../../../../img/72px_ok.png"); }
+.feedbackMessageError { background-image:url("../../../../img/48px_error.png"); }
+.feedbackMessageAlert { background-image:url("../../../../img/72px_Alert.png"); }
+.feedbackMessageConfirm { background-image:url("../../../../img/72px_confirm.png"); }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Core/Template/ShowFeedbackTemplate.php b/clients/ExpressoLite/src/accessible/Accessible/Core/Template/ShowFeedbackTemplate.php
new file mode 100644 (file)
index 0000000..03a9277
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite Accessible
+ * Template for feedback and confirm actions.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/general.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/ShowFeedbackTemplate.css" />
+    <link rel="icon" type="image/png" href="../img/favicon.png" />
+    <title>Aviso - ExpressoBr Acessível</title>
+</head>
+<body>
+
+<div id="top" name="top">
+    <div id="anchors" name="anchors" class="links systemLinks">
+        <nav class="contentAlign">
+            <ul>
+                <li><a href="<?= $VIEW->destinationUrl ?>" accesskey="v"><?= $VIEW->destinationText ?> [v]</a></li>
+            </ul>
+        </nav>
+    </div>
+</div>
+
+<h2 class="anchorsTitle">Mensagens</h2>
+<div id="feedback" name="feedback" class="<?= $VIEW->typeMsg ?>" >
+    <p id="feedbackMessage" name="feedbackMessage"> <?= $VIEW->message ?> </p>
+
+    <div id="buttons" name="buttons" class="links linkAsButton">
+        <hr />
+        <ul>
+            <?php FOREACH ($VIEW->buttons AS $BUTTON) : ?>
+                <li><a href="<?= $BUTTON->url ?>"><?= $BUTTON->value ?></a></li>
+            <?php ENDFOREACH; ?>
+        </ul>
+    <div>
+
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Core/Template/general.css b/clients/ExpressoLite/src/accessible/Accessible/Core/Template/general.css
new file mode 100644 (file)
index 0000000..2f71f55
--- /dev/null
@@ -0,0 +1,99 @@
+/*!
+ * Expresso Lite
+ * CSS for general templates.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Dantas <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+body { font:12pt Arial,sans-serif; color:#1A1A1A; overflow-y:scroll; margin:0 auto; margin-bottom:30px; }
+
+/* --- Layout --- */
+#top {
+    background-image:url("../../../../img/logo-expressobr-internal.png");
+    background-repeat:no-repeat;
+    background-position:20px 30px;
+    min-height:90px;
+    text-align:right;
+}
+
+.contentAlign { margin-left:10px; margin-right:10px; }
+
+.onlyForScreenReaders {
+    position: absolute !important;
+    clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
+    clip: rect(1px, 1px, 1px, 1px);
+}
+
+/* --- Headers --- */
+.anchorsTitle { font-weight:400; font-size:130%; background-color:#003852; color:#FFF; padding:7px 10px; text-decoration:none; }
+
+hr { border:0; height:1px; background-image:linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 56, 82, 1), rgba(0, 0, 0, 0)); }
+
+/* --- Form fields --- */
+label { color:#003852; }
+
+table ,input[type=button], input[type=text], input[type=email],
+input[type=password], input[type=submit], select, button, textarea {
+    font:11pt Arial,sans-serif; color:#1A1A1A;
+}
+input[type=text], input[type=password], input[type=email], textarea {
+    padding:8px;
+    width:calc(100% - 8px); /*-8px to account for left and right padding*/
+    border:1px solid #CCC;
+    display:block;
+    margin-bottom:15px;
+}
+textarea { height:250px; resize:none; overflow-y:scroll; }
+input[type=file] { width:100%; border:1px solid #CCC; box-shadow:2px 2px 2px rgba(0,0,0,0.2); }
+
+     /* Form fields behavior */
+input[type=text]:focus, input[type=password]:focus, input[type=email]:focus,
+input[type=file]:focus, input[type=file]:hover, textarea:focus {
+    box-shadow:0 0 4px #6EA2DE;
+    border:1px solid #6EA2DE;
+}
+
+     /* Form submit/buttons behavior */
+input[type=submit], input[type=button], button {
+    -moz-user-select:none;
+    background-color:#4479BA;
+    border: 1px solid #4272B2;
+    border-radius:1px;
+    color:#FFF;
+    cursor:pointer;
+    padding:5px 10px;
+}
+input[type=submit]:hover, input[type=button]:hover, button:hover{ background-color:#FFDB79; color:#000; }
+input[type=submit]:focus, input[type=button]:focus, button:focus { background-color:#FF6536; color:#FFF; }
+
+/* --- Links --- */
+
+.links li { list-style:none; }
+.links a { color:#000; text-decoration:none; }
+.links li a:hover { background-color:#FFDB79; color:#1B2026; }
+.links li a:focus { background-color:#FF6536; color:#FFF; text-decoration:underline; }
+
+    /* Pagination and Email actions */
+.linkAsButton li { display:inline; }
+.linkAsButton a {
+    -moz-user-select:none;
+    background-color:#4479BA;
+    border:1px solid #4272B2;
+    border-radius:1px;
+    color:#FFF;
+    cursor:pointer;
+    padding:5px 10px;
+}
+
+    /* Anchors for headers */
+.systemLinks ul { display:inline-block; text-align:left; }
+.systemLinks a { display:block; padding:5px 10px; border:1px solid #FFF; background-color:#F4F4F4; }
+
+/* --- Tables behavior--- */
+.clickableCell tr:hover td { background-color:#FFDB79; color:black; }
+
+.clickableCell a { text-decoration:none; color:#1B2026; display:block; padding:5px 10px; }
+.clickableCell a:focus { background-color:#FF6536; color:white; text-decoration:underline; }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Dispatcher.php b/clients/ExpressoLite/src/accessible/Accessible/Dispatcher.php
new file mode 100644 (file)
index 0000000..a5ac901
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Dispatches all page requests.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible;
+
+use \ReflectionClass;
+use \Exception;
+use ExpressoLite\Backend\TineSessionRepository;
+use ExpressoLite\Exception\NoTineSessionException;
+
+class Dispatcher
+{
+    /**
+     * @var REQUEST_NAMESPACE Constant that indicates in
+     *      wich request handlers will be searched for.
+     */
+    const REQUEST_NAMESPACE = 'Accessible\\';
+
+    /**
+     * @var LOGIN_REQUEST Request that directs to login screen
+     */
+    const LOGIN_REQUEST = 'Login.Main';
+
+    /**
+     * @var MAIL_REQUEST Request that directs to main mail screen
+     */
+    const MAIL_REQUEST = 'Mail.Main';
+
+    /**
+     * Processes raw HTTP requests. Used only in ../index.php page.
+     *
+     * @param array $httpRequest Should always be $_REQUEST object.
+     */
+    public static function processRawHttpRequest(array $httpRequestParams)
+    {
+        $request = isset($httpRequestParams['r']) ? $httpRequestParams['r'] : null;
+        $params = self::getParamsFromHttpRequest($httpRequestParams);
+        $isLoggedIn = TineSessionRepository::getTineSession()->isLoggedIn();
+
+        if ($isLoggedIn && $request === null) {
+            $request = self::MAIL_REQUEST;
+        }
+
+        if ($request === null) {
+            $request = self::LOGIN_REQUEST;
+        }
+
+        self::processRequest($request, (object) $params);
+    }
+
+    /**
+     * Calls another page, forwarding the given parameters.
+     *
+     * @param string   $request Name of the page to be called.
+     * @param stdClass $params  stdClass with parameters to be forwarded to page.
+     */
+    public static function processRequest($request, $params = null)
+    {
+        try {
+            $handlerClassName = self::REQUEST_NAMESPACE . str_replace('.', '\\', $request);
+            $handlerClass = new ReflectionClass($handlerClassName);
+            $requestHandler = $handlerClass->newInstance();
+            header('Pragma: no cache');
+            header('Cache-Control: no-cache');
+            $requestHandler->execute($params);
+        } catch (NoTineSessionException $ex) {
+            self::processRequest(self::LOGIN_REQUEST);
+        } catch (Exception $e) {
+            error_log($e->getTraceAsString());
+            die('<pre>' . $e->getMessage() . '<br />' . $e->getTraceAsString());
+        }
+    }
+
+    /**
+     * Filters the request name ($httpRequest['r']) to isolate the request parameters.
+     *
+     * @param  array $httpRequest Should always be $_REQUEST object.
+     * @return array              An indexed array with all request parameters but request name.
+     */
+    private static function getParamsFromHttpRequest(array $httpRequest)
+    {
+        $params = array();
+        foreach ($httpRequest as $key => $val) {
+            if ($key !== 'r') {
+                $params[$key] = $val;
+            }
+        }
+        return (object) $params;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Handler.php b/clients/ExpressoLite/src/accessible/Accessible/Handler.php
new file mode 100644 (file)
index 0000000..5e4ebf9
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Abstract class that defines expected behavior of http requests
+ * handlers created by Dispatcher. Also provides some utility methods.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible;
+
+use \ReflectionClass;
+use ExpressoLite\Exception\LiteException;
+
+abstract class Handler
+{
+    /**
+     * This is the main method of this class, which will execute all
+     * logic related to the request. Needless to say, all subclasses
+     * must necessarily implement it.
+     *
+     * @param  stdClass  $params Parameters to be forwarded.
+     * @return The request response.
+     */
+    abstract function execute($params);
+
+    /**
+     * Gets current directory path of this class.
+     *
+     * @return string Path to class.
+     */
+    private function getCurrentClassDir()
+    {
+        $classname = get_class($this);
+        $namespace = substr($classname, 0, strrpos($classname, "\\"));
+        return str_replace("\\", '/', $namespace);
+    }
+
+    /**
+     * Fills a template generating an HTML page.
+     *
+     * @param string   $templateName Name of the source template.
+     * @param stdClass $VIEW         Object with member variables to fill the template.
+     */
+    public function showTemplate($templateName, $VIEW = null)
+    {
+        if(!ctype_alpha($templateName)) { //only alphabetic chars
+            throw new LiteException('Invalid template name: ' . $templateName);
+        }
+
+        $templateFile = $this->getCurrentClassDir() . '/Template/' . $templateName . '.php';
+        if (!file_exists($templateFile)) {
+            throw new LiteException('Template not found: ' . $templateName);
+        }
+
+        include $templateFile;
+    }
+
+    /**
+     * Assemblies an URL with parameters to be used on the template.
+     *
+     * @param  string $action Action to be performed, like "Mail.Delete".
+     * @param  array  $params Parameters to be forwarded.
+     * @return string         URL ready to be used on the template.
+     */
+    public function makeUrl($action, array $params = array())
+    {
+        $retUrl = './?r=' . $action;
+        foreach ($params as $key => $val) {
+            $retUrl .= '&' . $key . '=' . $val;
+        }
+        return $retUrl;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Login/Login.php b/clients/ExpressoLite/src/accessible/Accessible/Login/Login.php
new file mode 100644 (file)
index 0000000..9603690
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Handler for Login calls.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Login;
+
+use Accessible\Handler;
+use Accessible\Dispatcher;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use Accessible\Core\ShowFeedback;
+
+class Login extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        $user = $params->user;
+        $password = $params->pwd;
+        $liteRequestProcessor = new LiteRequestProcessor();
+        $response = $liteRequestProcessor->executeRequest('Login', (object) array(
+            'user' => $user,
+            'pwd' => $password
+        ));
+
+        if ($response->success === true) {
+            Dispatcher::processRequest('Mail.Main', $params);
+        } else {
+            Dispatcher::processRequest('Core.ShowFeedback', (object) array(
+                'typeMsg' => ShowFeedback::MSG_ERROR,
+                'message' => 'Login falhou! Usuário ou senha inválidos.',
+                'destinationText' => 'Voltar para página de login',
+                'destinationUrl' => (object) array(
+                    'action' => 'Login.Main'
+                )
+            ));
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Login/Logoff.php b/clients/ExpressoLite/src/accessible/Accessible/Login/Logoff.php
new file mode 100644 (file)
index 0000000..bc6467c
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Handler for Logoff calls.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Login;
+
+use Accessible\Handler;
+use Accessible\Dispatcher;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+use Accessible\Core\ShowFeedback;
+
+class Logoff extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        if (TineSessionRepository::getTineSession()->isLoggedIn()) {
+            $liteRequestProcessor = new LiteRequestProcessor();
+            $liteRequestProcessor->executeRequest('Logoff', (object) array());
+        }
+
+        Dispatcher::processRequest('Core.ShowFeedback', (object) array(
+            'typeMsg' => ShowFeedback::MSG_SUCCESS,
+            'message' => 'Saída realizada com sucesso.',
+            'destinationText' => 'Acessar a página de login',
+            'destinationUrl' => (object) array(
+                'action' => 'Login.Main'
+            )
+        ));
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Login/Main.php b/clients/ExpressoLite/src/accessible/Accessible/Login/Main.php
new file mode 100644 (file)
index 0000000..946cc34
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Loads login template.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Login;
+
+use Accessible\Handler;
+
+class Main extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        $lastLogin = isset($_COOKIE['user']) ? urldecode($_COOKIE['user']) : '';
+        $this->showTemplate('MainTemplate', (object) array(
+            'lastLogin' => $lastLogin
+        ));
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Login/Template/MainTemplate.css b/clients/ExpressoLite/src/accessible/Accessible/Login/Template/MainTemplate.css
new file mode 100644 (file)
index 0000000..f4154b0
--- /dev/null
@@ -0,0 +1,44 @@
+/*!
+ * Expresso Lite
+ * CSS for Main.php template.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+#credent {
+    height:560px;
+    width:350px;
+    margin:0 auto;
+    margin-top:30px;
+    border-radius:12px;
+    box-shadow:2px 2px 6px #999;
+    background-color:white;
+    background:url("../../../../img/logo-expressobr-bottom.png") no-repeat bottom;
+}
+
+#logo_top { margin-top:20px; margin-left:50px; }
+
+#frmLogin {
+    margin-left:20px;
+    margin-right:20px;
+    padding-top:30px;
+    border-top:1px solid #E8E8E8;
+}
+
+.frmLoginFields { margin-left:10px; margin-right:20px; margin-bottom:25px; }
+
+#frmLoginSubmit { text-align:center; margin-top:52px; }
+
+#expressoBrAccess {
+    height:24px;
+    line-height:24px;
+    text-align:center;
+    font-size:90%;
+    margin-top:20px;
+}
+#expressoBrAccess a { text-decoration:none; color:#006594; }
+#expressoBrAccess a:hover { text-decoration:underline; }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Login/Template/MainTemplate.php b/clients/ExpressoLite/src/accessible/Accessible/Login/Template/MainTemplate.php
new file mode 100644 (file)
index 0000000..63de6de
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite Accessible
+ * Entry page for accessible module.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />
+    <link rel="icon" type="image/png" href="../img/favicon.png" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/general.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Login/Template/MainTemplate.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Mail/Template/ComposeMessageTemplate.css" />
+    <title>Login - ExpressoBr Acessível</title>
+</head>
+<body>
+
+<div id="credent" name="credent">
+    <img id="logo_top" name="logo_top" src="../img/logo-expressobr-top.png" alt="Logotipo do ExpressoBr Acessível"/>
+    <form action="." id="frmLogin" name="frmLogin"  class="form" method="post">
+        <input type="hidden" id="r" name="r" value="Login.Login">
+        <div class="frmLoginFields">
+            <label for="user">Usuário: </label>
+            <input id="user" name="user" type="text" placeholder="Digite o email do usuário" value="<?= $VIEW->lastLogin ?>" tabindex="1" required="required" />
+        </div>
+
+        <div class="frmLoginFields">
+             <label for="pwd">Senha: </label>
+             <input id="pwd" name="pwd" type="password" placeholder="Digite a senha" tabindex="2" required="required" />
+        </div>
+
+        <div id="frmLoginSubmit" name="formLoginSubmit">
+             <input type="submit" value="login" tabindex="3"/>
+        </div>
+    </form>
+</div>
+
+<div id="expressoBrAccess" name="expressoBrAccess">
+    <a title="Ir para o ExpressoBr" accesskey="e" href="../">Ir para o ExpressoBr [e]</a>
+</div>
+
+</body>
+</html>
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/ComposeMessage.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/ComposeMessage.php
new file mode 100644 (file)
index 0000000..e189a81
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Email composition.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+
+class ComposeMessage extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        // Retrieve original message, if replying or forwarding.
+        if (isset($params->reply) || isset($params->replyAll) || isset($params->forward)) {
+            $lrp = new LiteRequestProcessor();
+            $msg = $lrp->executeRequest('GetMessage', (object) array(
+                'id' => $params->messageId
+            ));
+        } else {
+            $msg = null; // compose new mail, not reply/forward
+        }
+
+        $this->showTemplate('ComposeMessageTemplate', (object) array(
+            'folderId' => $params->folderId,
+            'folderName' => $params->folderName,
+            'page' => isset($params->page) ? $params->page : '1',
+            'actionText' => $this->actionText($params),
+            'subject' => $this->formatSubject($params, $msg),
+            'to' => (isset($params->reply) || isset($params->replyAll)) ? $msg->from_email : '',
+            'cc' => isset($params->replyAll) ? $this->formatCcAddresses($params, $msg) : '',
+            'replyToId' => (isset($params->reply) || isset($params->replyAll)) ? $params->messageId : '',
+            'forwardFromId' => isset($params->forward) ? $params->messageId : '',
+            'signature' => TineSessionRepository::getTineSession()->getAttribute('Expressomail.signature'),
+            'quotedBody' => $this->prepareQuotedMessage($params, $msg),
+            'lnkBackText' => (isset($params->reply) || isset($params->replyAll) || isset($params->forward)) ?
+                'mensagem de origem' : $params->folderName,
+            'lnkBackUrl' => $this->returnLink($params),
+            'lnkSendMessageAction' => $this->makeUrl('Mail.SendMessage'),
+            'existingAttachments' => isset($params->attachments) ? json_decode($params->attachments) : array()
+        ));
+    }
+
+    /**
+     * Returns text for the action being performed on this message composition.
+     *
+     * @param  stdClass $params Request parameters.
+     * @return string           Text of action.
+     */
+    private function actionText($params)
+    {
+        if (isset($params->reply)) {
+            return 'Responder';
+        } else if (isset($params->replyAll)) {
+            return 'Responder a todos';
+        } else if (isset($params->forward)) {
+            return 'Encaminhar';
+        }
+        return 'Compor';
+    }
+
+    /**
+     * Formats subject address field, if needed.
+     *
+     * @param  stdClass $params Request parameters.
+     * @param  stdClass $msg    Message object, if replied or forwarded.
+     * @return string           Subject of message.
+     */
+    private function formatSubject($params, $msg = null)
+    {
+        if ((isset($params->reply) || isset($params->replyAll)) && $msg !== null) {
+            return 'Re: ' . $msg->subject;
+        } else if (isset($params->forward) && $msg !== null) {
+            return 'Fwd: ' . $msg->subject;
+        }
+        return ''; // composing new message, no subject is given
+    }
+
+    /**
+     * Returns the URL to go back to previous page.
+     *
+     * @param  stdClass $params Request parameters.
+     * @return string           The return URL.
+     */
+    private function returnLink($params)
+    {
+        if (isset($params->reply) || isset($params->replyAll) || isset($params->forward)) {
+            return $this->makeUrl('Mail.OpenMessage', array(
+                'messageId' => $params->messageId,
+                'folderId' => $params->folderId,
+                'folderName' => $params->folderName,
+                'isTrashFolder' => ($params->folderName === 'Inbox / Lixeira') ? 1 : 0,
+                'page' => $params->page
+            ));
+        }
+
+        return $this->makeUrl('Mail.Main', array(
+            'folderId' => $params->folderId,
+            'page' => $params->page
+        ));
+    }
+
+    /**
+     * Formats "Cc" address field, if needed.
+     *
+     * @param  stdClass $params Request parameters.
+     * @param  stdClass $msg    Message object, if replied or forwarded.
+     * @return string           The return Cc addresses formatted.
+     */
+    private function formatCcAddresses($params, $msg = null)
+    {
+        $userEmail = TineSessionRepository::getTineSession()->getAttribute('Expressomail.email');
+
+        if (($key = array_search($userEmail, $msg->to)) !== false) {
+            unset($msg->to[$key]);
+        }
+
+        if (isset($params->replyAll) && $msg !== null) {
+            return implode(', ', array_merge($msg->to, $msg->cc));
+        }
+        return '';
+    }
+
+    /**
+     * Formats replied/forwarded message body texts.
+     *
+     * @param  stdClass $params Request parameters.
+     * @param  stdClass $msg    Message object, if replied or forwarded.
+     * @return string           Formatted body text.
+     */
+    private function prepareQuotedMessage($params, $msg = null)
+    {
+        if (is_null($msg)) {
+            return '';
+        }
+
+        $formatedDate = date('d/m/Y H:i', strtotime($msg->received));
+        if ((isset($params->reply) || isset($params->replyAll)) && $msg !== null) {
+            return '<br />Em ' . $formatedDate . ', ' .
+                $msg->from_name . ' escreveu:' .
+                '<blockquote>' . $msg->body->message . '<br />' .
+                ($msg->body->quoted !== null ? $msg->body->quoted : '') .
+                '</blockquote>';
+        } else if (isset($params->forward) && $msg !== null) {
+            return '<br />-----Mensagem original-----<br />' .
+                '<b>Assunto:</b> ' . $msg->subject . '<br />' .
+                '<b>Remetente:</b> "' . $msg->from_name . '" &lt;' . $msg->from_email . '&gt;<br />' .
+                '<b>Para:</b> ' . implode(', ', $msg->to) . '<br />' .
+                (!empty($msg->cc) ? '<b>Cc:</b> ' . implode(', ', $msg->cc) . '<br />' : '') .
+                '<b>Data:</b> ' . $formatedDate . '<br /><br />' .
+                $msg->body->message . '<br />' .
+                ($msg->body->quoted !== null ? $msg->body->quoted : '');
+        }
+        return '';
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/ConfirmMessageAction.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/ConfirmMessageAction.php
new file mode 100644 (file)
index 0000000..4ae7bba
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * All message action that needs confirmation, checking whether one
+ * or more messages have been previously marked, before completion.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use Accessible\Dispatcher;
+use Accessible\Core\ShowFeedback;
+use Accessible\Mail\ProcessMessageAction;
+use Accessible\Core\MessageIds;
+
+class ConfirmMessageAction extends Handler
+{
+    /**
+     * @var OPTION_OK.
+     */
+    const OPTION_OK = 'Ok';
+
+    /**
+     * @var OPTION_CANCEL.
+     */
+    const OPTION_CANCEL = 'Cancelar';
+
+    /**
+     * @var CHECKED_MESSAGES_EMPTY.
+     */
+    const CHECKED_MESSAGES_EMPTY = 'Nenhuma mensagem foi selecionada. Por favor, retorne e selecione uma ou mais mensagens.';
+
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        if (!$this->hasAtLeastOneMessageChecked($params)) {
+            $messageIds = '';
+            $typeFeedbackMsg = ShowFeedback::MSG_ALERT;
+        } else {
+            $typeFeedbackMsg = ShowFeedback::MSG_CONFIRM;
+            $messageIds = MessageIds::paramsToString($params);
+        }
+
+        $messageFeedback = $this->prepareConfirmMessageAction($params);
+        Dispatcher::processRequest('Core.ShowFeedback', (object) array(
+            'typeMsg' => $typeFeedbackMsg,
+            'message' => $messageFeedback,
+            'destinationText' => 'Voltar para ' . $params->folderName,
+            'destinationUrl' => (object) array (
+                'action' => 'Mail.Main',
+                'params' => array (
+                    'folderId' => $params->folderId,
+                    'folderName' => $params->folderName,
+                    'page' => $params->page
+                )
+            ),
+            'buttons' => $this->prepareConfirmationButtons($messageIds, $params)
+        ));
+    }
+
+   /**
+    * Verify if at least one message was marked before an action execution.
+    * When one or more messages are previously marked, in a checkbox from messages display,
+    * there will be passed one or more attributes fcalled 'check_XX' in wich 'XX' is
+    * dynamically assigned and its value contains a message id
+    *
+    * @param object $params Contains 'check_' attributes each representing a message id
+    * @return boolean true if at least one message was checked, false otherwise
+    */
+    private function hasAtLeastOneMessageChecked($params)
+    {
+        $objAttributeKeysToArray = array_keys(get_object_vars($params));
+        return count(preg_grep('/check_/', $objAttributeKeysToArray)) > 0;
+    }
+
+    /**
+     * Sets the action process to be executed and creates the confirmation button options
+     * for message actions that requires confirmation
+     *
+     * @param string $messageIds Not empty and has zero or more message ids(comma separated)
+     * @param object $params Contains the request confirmation buttons parameters
+     * @return null if no message id is passed, object array Containing confirmation buttons otherwise
+     */
+    private function prepareConfirmationButtons($messageIds, $params)
+    {
+        if (empty($messageIds) || is_null($params)) {
+            return null;
+        }
+
+        return (object) array(
+            (object) array(
+                'action' => 'Mail.ProcessMessageAction',
+                'params' => array(
+                    'folderId' => $params->folderId,
+                    'folderName' => $params->folderName,
+                    'page' => $params->page,
+                    'messageIds' => $messageIds,
+                    'actionProcess' => $params->actionProcess,
+                    'confirmOption' => self::OPTION_OK
+                )
+            ),
+            (object) array (
+                'action' => 'Mail.ProcessMessageAction',
+                'params' => array(
+                    'folderId' => $params->folderId,
+                    'folderName' => $params->folderName,
+                    'page' => $params->page,
+                    'messageIds' => $messageIds,
+                    'actionProcess' => $params->actionProcess,
+                    'confirmOption' => self::OPTION_CANCEL
+                )
+            )
+        );
+    }
+
+    /**
+     * Prepares the confirmation text message to an action execution that requires it
+     *
+     *
+     * @param string $messageIds Not empty and has one or more message ids(comma separated)
+     * @return string Confirmation text message
+     */
+    private function prepareConfirmMessageAction($params)
+    {
+        if (!$this->hasAtLeastOneMessageChecked($params)) {
+            return self::CHECKED_MESSAGES_EMPTY;
+        } else {
+            $messageIds = MessageIds::paramsToString($params);
+            $countMessagesId = $this->getNumberOfMessageIds($messageIds);
+
+            $messagesSelected = $countMessagesId === 1 ?
+                '1 mensagem foi selecionada.' :
+                "$countMessagesId mensagens foram selecionadas.";
+
+            return "$messagesSelected<br /> Deseja ". strtolower($params->actionProcess) . '?';
+        }
+    }
+
+    /**
+     * Creates a string comma separated of message ids
+     *
+     * @param object $params Contains 'check_' attributes each representing a message id
+     * @return string Comma separated of message ids
+     */
+    private function toStringOfMessagesId($params)
+    {
+        $objVarToArray = get_object_vars($params);
+        foreach($objVarToArray as $key => $value) {
+            if (!preg_match('/check_/', $key)) {
+                unset($objVarToArray[$key]);
+            }
+        }
+
+        return implode(',', $objVarToArray);
+    }
+
+    /**
+     * Gets the number of messages id
+     *
+     * @param string $messageIds Not empty and has one or more message ids(comma separated)
+     * @return int A value indicating the number of message ids
+     */
+    private function getNumberOfMessageIds($messageIds)
+    {
+        return $messageIds === '' ? 0 : count(explode(',', $messageIds));
+    }
+
+    /**
+     * Validates the action confirmation option of the request
+     *
+     * @param object $params Contains the request confirm option
+     * @return boolean true If the request confirm option is equal to 'OPTION_OK', false otherwise
+     */
+    public static function isActionConfirmOk($params)
+    {
+        return $params->confirmOption === self::OPTION_OK;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/DeleteMessage.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/DeleteMessage.php
new file mode 100644 (file)
index 0000000..80d87d8
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Delete an email message.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+use Accessible\Dispatcher;
+use Accessible\Core\ShowFeedback;
+
+class DeleteMessage extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+
+    /**
+     * @var TRASH_FOLDER Global name of trash folder.
+     */
+    const TRASH_FOLDER = 'INBOX/Trash';
+
+    public function execute($params)
+    {
+        $folders = TineSessionRepository::getTineSession()->getAttribute('folders');
+        $isTrashFolder = FALSE;
+        foreach ($folders as $fol) {
+            if ($fol->id === $params->folderId) {
+                $isTrashFolder = $fol->globalName === self::TRASH_FOLDER;
+                break;
+            }
+        }
+
+        $liteRequestProcessor = new LiteRequestProcessor();
+        $message = $liteRequestProcessor->executeRequest('DeleteMessages', (object) array(
+            'messages' => $params->messageId,
+            'forever' => $isTrashFolder ? '1' : '0'
+        ));
+
+        $outMsg = ($isTrashFolder) ?
+            'Mensagem apagada com sucesso.' :
+            'Mensagem movida para lixeira.';
+
+        Dispatcher::processRequest('Core.ShowFeedback', (object) array (
+            'typeMsg' => ShowFeedback::MSG_SUCCESS,
+            'message' => $outMsg,
+            'destinationText' => 'Voltar para ' . $params->folderName,
+            'destinationUrl' => (object) array(
+                'action' => 'Mail.Main',
+                'params' => array (
+                   'folderId' => $params->folderId
+                )
+            )
+        ));
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Main.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/Main.php
new file mode 100644 (file)
index 0000000..2ec033a
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Manipulates data for the application main screen, loading some
+ * especific email folder information and headlines. Also provides
+ * access to others functionalities as write a new email and do
+ * system logoff.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+use Accessible\Core\DateUtils;
+use Accessible\Mail\ProcessMessageAction;
+
+class Main extends Handler
+{
+    /**
+     * @var REQUEST_LIMIT Max number of requested items.
+     */
+    const REQUEST_LIMIT = 50;
+
+    /**
+     * @var TRASH_FOLDER Global name of trash folder.
+     */
+    const TRASH_FOLDER = 'INBOX/Trash';
+
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        $folderId = isset($params->folderId) ? $params->folderId : null;
+        $curFolder = $this->getFolder($folderId);
+
+        if (!isset($params->page)) { // one-based index of current email pagination
+            $params->page = 1; // if no page is specified, start listing from page 1
+        }
+        $headlines = $this->retrieveHeadlines($curFolder, $params->page);
+        $start = (($params->page - 1) * self::REQUEST_LIMIT) + 1 ;
+
+        $noEmail = (count($headlines) === 0);
+
+        $limit = $start + count($headlines) - 1;
+        $this->showTemplate('MainTemplate', (object) array(
+            'noEmail'   => $noEmail,
+            'curFolder' => $curFolder,
+            'headlines' => $headlines,
+            'page' => $params->page,
+            'start' => $start,
+            'limit' => $limit,
+            'requestLimit' => self::REQUEST_LIMIT,
+            'lnkRefreshFolder' => $this->makeUrl('Mail.Main', array(
+                'folderId' => $curFolder->id,
+                'page' => $params->page
+            )),
+            'lnkChangeFolder' => $this->makeUrl('Mail.OpenFolder', array(
+                'folderId' => $curFolder->id,
+                'folderName' => $curFolder->localName,
+                'page' => $params->page
+            )),
+            'lnkComposeMessage' => $this->makeUrl('Mail.ComposeMessage', array(
+                'folderId' => $curFolder->id,
+                'folderName' => $curFolder->localName,
+                'page' => $params->page
+            )),
+            'lnkLogoff' => $this->makeUrl('Login.Logoff'),
+            'lnkPrevPage' => $this->makeUrl('Mail.Main', array(
+                'folderId' => $curFolder->id,
+                'page' => $params->page - 1
+            )),
+            'lnkNextPage' => $this->makeUrl('Mail.Main', array(
+                'folderId' => $curFolder->id,
+                'page' => $params->page + 1
+            )),
+            'action_mark_unread' => ProcessMessageAction::ACTION_MARK_UNREAD,
+            'lnkConfirmMessageAction' => $this->makeUrl('Mail.ConfirmMessageAction', array(
+                'folderId' => $curFolder->id,
+                'folderName' => $curFolder->localName,
+                'page' => $params->page,
+            ))
+        ));
+    }
+
+    /**
+     * Get folder from Folder ID
+     *
+     * @param int $folderId
+     * @return array of current folder
+     */
+    private function getFolder($folderId)
+    {
+        $folders = TineSessionRepository::getTineSession()->getAttribute('folders');
+        if ($folderId === null || $folders === null) {
+            $liteRequestProcessor = new LiteRequestProcessor();
+            $response = $liteRequestProcessor->executeRequest('SearchFolders', (object) array());
+            $curFolder = (object) array(
+                'id' => $response[0]->id,
+                'localName' => $response[0]->localName,
+                'globalName' => $response[0]->globalName,
+                'totalMails' => $response[0]->totalMails,
+                'unreadMails' => $response[0]->unreadMails
+            );
+
+            if (!isset($folders)) {
+                $folders[] = (object) array(
+                    'id' => $curFolder->id,
+                    'title' => 'Esta pasta contém ' . $curFolder->totalMails . ' emails' . ' e ' . $curFolder->unreadMails . ' emails não lido',
+                    'localName' => $curFolder->localName,
+                    'globalName' => $curFolder->globalName,
+                    'totalMails' => $curFolder->totalMails,
+                    'unreadMails' => $curFolder->unreadMails
+                );
+                TineSessionRepository::getTineSession()->setAttribute('folders', $folders);
+            }
+        } else {
+            $liteRequestProcessor = new LiteRequestProcessor();
+            $response = $liteRequestProcessor->executeRequest('UpdateMessageCache', (object) array(
+                'folderId' => $folderId
+            ));
+
+            foreach ($folders as $tmp) {
+                if ($tmp->id === $folderId) {
+                    $curFolder = (object) array(
+                        'id' => $folderId,
+                        'localName' => $tmp->localName,
+                        'globalName' => $tmp->globalName,
+                        'totalMails' => $response->totalMails,
+                        'unreadMails' => $response->unreadMails
+                    );
+                    break;
+                }
+            }
+        }
+        return $curFolder;
+    }
+
+    /**
+     * Returns an array containing headlines information and links for actions
+     *
+     * @param string $curFolder
+     * @param int $curPage The current page of headlines pagination
+     * @return array of headlines
+     */
+    private function retrieveHeadlines($curFolder, $curPage)
+    {
+        $liteRequestProcessor = new LiteRequestProcessor();
+        $headlines = $liteRequestProcessor->executeRequest('SearchHeadlines', (object) array(
+            'folderIds' => $curFolder->id,
+            'start' => ($curPage - 1) * self::REQUEST_LIMIT,
+            'limit' => self::REQUEST_LIMIT
+        ));
+
+        foreach ($headlines as &$headl) {
+            $headl->received = DateUtils::getFormattedDate($headl->received);
+            $headl->subject = trim($headl->subject);
+            if ($headl->subject === '') {
+                $headl->subject = '(sem assunto)';
+            }
+
+            $headl->lnkOpen = $this->makeUrl('Mail.OpenMessage', array(
+                'messageId' => $headl->id,
+                'folderId' => $curFolder->id,
+                'folderName' => $curFolder->localName,
+                'page' => $curPage
+            ));
+        }
+        return $headlines;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/MarkMessageAsUnread.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/MarkMessageAsUnread.php
new file mode 100644 (file)
index 0000000..9902439
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Changes flags of message.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use Accessible\Dispatcher;
+use Accessible\Core\ShowFeedback;
+use Accessible\Core\MessageIds;
+
+class MarkMessageAsUnread extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        if ($params->messageIds === '') { // No message id was specified
+            $typeMsg = ShowFeedback::MSG_ERROR;
+            $outMsg = 'Não foi possível marcar mensagem como não lida';
+        } else {
+            $liteRequestProcessor = new LiteRequestProcessor();
+            $message = $liteRequestProcessor->executeRequest('MarkAsRead', (object) array(
+                'ids' => $params->messageIds,
+                'asRead' => '0'
+            ));
+
+            $typeMsg = ShowFeedback::MSG_SUCCESS;
+            $msgCount = MessageIds::messageCount($params->messageIds);
+            $outMsg = $msgCount == 1 ?
+                '1 mensagem marcada como não lida com sucesso.' :
+                "$msgCount mensagens marcadas como não lida com sucesso.";
+        }
+
+        Dispatcher::processRequest('Core.ShowFeedback', (object) array(
+            'typeMsg' => $typeMsg,
+            'message' => $outMsg,
+            'destinationText' => 'Voltar para ' . $params->folderName,
+            'destinationUrl' => (object) array(
+                'action' => 'Mail.Main',
+                'params' => array(
+                    'folderId' => $params->folderId
+                )
+            )
+        ));
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/MoveMessage.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/MoveMessage.php
new file mode 100644 (file)
index 0000000..4ef493b
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Move one or more messages to the specified folder.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use Accessible\Dispatcher;
+use Accessible\Core\ShowFeedback;
+
+class MoveMessage extends Handler
+{
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute($params)
+    {
+        $liteRequestProcessor = new LiteRequestProcessor();
+        $message = $liteRequestProcessor->executeRequest('MoveMessages', (object) array(
+            'folder' => $params->folderId,
+            'messages' => $params->messageIds
+        ));
+
+        $outMsg = count(explode(',',$params->messageIds)) > 1 ?
+            count(explode(',',$params->messageIds)) . ' mensagens movidas para a pasta ' . $params->folderName . ' com sucesso.' :
+            count(explode(',',$params->messageIds)) . ' mensagem movida para a pasta ' . $params->folderName . ' com sucesso.';
+
+        Dispatcher::processRequest('Core.ShowFeedback', (object) array (
+            'typeMsg' => ShowFeedback::MSG_SUCCESS,
+            'message' => $outMsg,
+            'destinationText' => 'Voltar para ' . $params->currentFolderName,
+            'destinationUrl' => (object) array(
+                'action' => 'Mail.Main',
+                'params' => array (
+                   'folderId' => $params->currentFolderId,
+                )
+            )
+        ));
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/OpenFolder.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/OpenFolder.php
new file mode 100644 (file)
index 0000000..577c357
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Shows all folders for the user to choose one and move message to another folder.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Edgar de Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\TineSessionRepository;
+use ExpressoLite\Backend\LiteRequestProcessor;
+
+class OpenFolder extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        $liteRequestProcessor = new LiteRequestProcessor();
+        $response = $liteRequestProcessor->executeRequest('SearchFolders', (object) array(
+            'recursive' => true
+        ));
+
+        $folders = $this->flatFolderTree($response, $params);
+        TineSessionRepository::getTineSession()->setAttribute('folders', $folders);
+
+        $this->showTemplate('OpenFolderTemplate', (object) array(
+            'folders' => $folders,
+            'folderName' => $params->folderName,
+            'lnkRefreshFolder' => $this->makeUrl('Mail.Main', array(
+                'folderName' => $params->folderName,
+                'folderId' =>  $params->folderId,
+                'page' => $params->page
+            )),
+            // Link to back to the message that will no longer be moved
+            // only visible from the message view
+            'lnkRefreshMessage' => $this->makeUrl('Mail.OpenMessage', array(
+                'folderName' => $params->folderName,
+                'folderId' => $params->folderId,
+                'page' => $params->page,
+                'messageId' => isset($params->messageIds) ? $params->messageIds : ''
+            )),
+            'isMsgBeingMoved' => isset($params->isMsgBeingMoved) ? true : false
+        ));
+    }
+
+    /**
+     * Flat folder tree
+     *
+     * @param array $arrFolders
+     * @param string $parents
+     * @return array of folder tree
+     */
+    private function flatFolderTree($arrFolders, $params, $parents = '')
+    {
+        $retFolders = array();
+        $handler = isset($params->isMsgBeingMoved) ? 'Mail.MoveMessage' : 'Mail.Main';
+        foreach ($arrFolders as $fol) {
+            $writtenParents = ($parents === '') ? '' : $parents . ' / ';
+
+            $retFolders[] = (object) array(
+                'id' => $fol->id,
+                'lnkOpenFolder' => $this->makeUrl($handler, array(
+                    'folderId' => $fol->id,
+                    'folderName' => $fol->localName,
+                    'currentFolderName' => $params->folderName,
+                    'currentFolderId' => $params->folderId,
+                    // only necessary for moving messages to any folder
+                    'messageIds' => isset($params->messageIds) ? $params->messageIds : ''
+                )),
+                'title' => 'Abrir pasta ' . $fol->localName .', contém ' . $fol->totalMails . ' emails' . ' e ' . $fol->unreadMails . ' emails não lido',
+                'localName' => $writtenParents . $fol->localName,
+                'globalName' => $fol->globalName,
+                'totalMails' => $fol->totalMails,
+                'unreadMail' => $fol->unreadMails
+            );
+            if (count($fol->subfolders) > 0) {
+                $retFolders = array_merge($retFolders, $this->flatFolderTree($fol->subfolders, $params ,$writtenParents . $fol->localName));
+            }
+        }
+        return $retFolders;
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/OpenMessage.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/OpenMessage.php
new file mode 100644 (file)
index 0000000..4432cdf
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Opens an email message.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+use Accessible\Core\DateUtils;
+
+class OpenMessage extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        $liteRequestProcessor = new LiteRequestProcessor();
+        $message = $liteRequestProcessor->executeRequest('GetMessage', (object) array(
+            'id' => $params->messageId
+        ));
+
+        $markAsRead = $liteRequestProcessor->executeRequest('MarkAsRead', (object) array(
+            'ids' => $params->messageId,
+            'asRead' => '1'
+        ));
+
+        if ($message->subject == '') {
+            $message->subject = '(sem assunto)';
+        }
+
+        $attachments = $this->formatAttachments($message->attachments, $params->messageId);
+
+        $this->showTemplate('OpenMessageTemplate', (object) array(
+            'folderName' => $params->folderName,
+            'message' => $message,
+            'formattedDate' => DateUtils::getFormattedDate(strtotime($message->received), true),
+            'page' => $params->page,
+            'lnkBack' => $this->makeUrl('Mail.Main', array(
+                'folderId' => $params->folderId,
+                'page' => $params->page
+            )),
+            'lnkDelete' => $this->makeUrl('Mail.DeleteMessage', array(
+                'folderName' => $params->folderName,
+                'messageId' => $params->messageId,
+                'folderId' => $params->folderId
+            )),
+            'lnkMark' => $this->makeUrl('Mail.MarkMessageAsUnread', array(
+                'folderName' => $params->folderName,
+                'messageIds' => $params->messageId,
+                'folderId' => $params->folderId
+            )),
+            'lnkReply' => $this->makeUrl('Mail.ComposeMessage', array(
+                'folderId' => $params->folderId,
+                'folderName' => $params->folderName,
+                'page' => $params->page,
+                'messageId' => $params->messageId,
+                'reply' => 'yes',
+                'attachments' => urlencode(json_encode($attachments))
+            )),
+            'lnkReplyAll' => $this->makeUrl('Mail.ComposeMessage', array(
+                'folderId' => $params->folderId,
+                'folderName' => $params->folderName,
+                'page' => $params->page,
+                'messageId' => $params->messageId,
+                'replyAll' => 'yes',
+                'attachments' => urlencode(json_encode($attachments))
+            )),
+            'lnkForward' => $this->makeUrl('Mail.ComposeMessage', array(
+                'folderId' => $params->folderId,
+                'folderName' => $params->folderName,
+                'page' => $params->page,
+                'messageId' => $params->messageId,
+                'forward' => 'yes',
+                'attachments' => urlencode(json_encode($attachments))
+            )),
+            'attachmentsForExhibition' => $attachments,
+            'lnkMoveMsgToFolder' => $this->makeUrl('Mail.OpenFolder', array(
+                'folderId' => $params->folderId,
+                'folderName' => $params->folderName,
+                'page' => $params->page,
+                'messageIds' => $params->messageId,
+                'isMsgBeingMoved' => true
+            ))
+        ));
+    }
+
+    /**
+     * Creates attachments information such as extension, filename, size and
+     * download link for each attachment.
+     *
+     * @param array  $attachments      Attachments array from message object.
+     * @param string $msgId            ID of current message.
+     * @return array $formattedAttachments
+     */
+    private function formatAttachments(array $attachments, $msgId)
+    {
+        if (is_null($attachments) || empty($msgId)) {
+            return array();
+        }
+
+        $formattedAttachments = array();
+        foreach ($attachments as $attach) {
+            $attach->accessibleExtension = strrchr($attach->filename, '.');
+            $attach->accessibleFileName = basename ($attach->filename, $attach->accessibleExtension);
+            $attach->accessibleFileSize = $this->sizeFilter($attach->size);
+
+            $attach->lnkDownload = '../api/ajax.php?' .
+                'r=downloadAttachment&' .
+                'forceDownload&' .
+                'fileName=' . urlencode($attach->filename) . '&' .
+                'messageId=' . $msgId . '&' .
+                'partId=' . $attach->partId;
+            array_push($formattedAttachments, $attach);
+        }
+        return $formattedAttachments;
+    }
+
+    /**
+     * Filters file size in Bytes, KBytes, MBytes, GBytes, TBytes.
+     *
+     * @param string $fileSizeBytes       represents size file in bytes.
+     */
+    public function sizeFilter($fileSizeBytes)
+    {
+        $base = log($fileSizeBytes, 1024);
+        $suffixes = array('bytes', 'KB', 'MB', 'GB', 'TB');
+
+        return round(pow(1024, $base - floor($base)), 0) . ' ' . $suffixes[floor($base)];
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/ProcessMessageAction.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/ProcessMessageAction.php
new file mode 100644 (file)
index 0000000..af68a63
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Process any message action, checking the action to be performed
+ * and dispatching the request to the correct responsible handler.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use Accessible\Dispatcher;
+use Accessible\Core\ShowFeedback;
+use Accessible\Mail\ConfirmMessageAction;
+
+class ProcessMessageAction extends Handler
+{
+    /**
+     * @var ACTION_MARK_UNREAD.
+     */
+    const ACTION_MARK_UNREAD = 'Marcar como não lido';
+
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        //check if the action can be executed
+        if (!ConfirmMessageAction::isActionConfirmOk($params)) {
+            // Message action will not be complete
+            Dispatcher::processRequest('Mail.Main', $params);
+            return;
+        }
+
+        // check wich action to be executed
+        if($params->actionProcess === self::ACTION_MARK_UNREAD) {
+            Dispatcher::processRequest('Mail.MarkMessageAsUnread', $params);
+        } else {
+            // action does not exist
+            Dispatcher::processRequest('Core.ShowFeedback', (object) array (
+                'typeMsg' => ShowFeedback::MSG_ERROR,
+                'message' => 'Ação requisitada não existe',
+                'destinationText' => 'Voltar para ' . $params->folderName,
+                'destinationUrl' => (object) array(
+                    'action' => 'Mail.Main',
+                    'params' => array (
+                        'folderId' => $params->folderId,
+                        'page' => $params->page
+                    )
+                )
+            ));
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/SendMessage.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/SendMessage.php
new file mode 100644 (file)
index 0000000..84b9f00
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Sends a message.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace Accessible\Mail;
+
+use Accessible\Dispatcher;
+use Accessible\Handler;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+use Accessible\Core\ShowFeedback;
+
+class SendMessage extends Handler
+{
+    /**
+     * @see Accessible\Handler::execute
+     */
+    public function execute($params)
+    {
+        // Treat uploaded files, if any.
+        $upFiles = array();
+        foreach ($_FILES as $upf) {
+            $processed = $this->uploadFile($upf);
+            if ($processed !== null) {
+                $upFiles[] = $processed;
+            }
+        }
+
+        // Retrieve original message, if replying or forwarding.
+        $lrp = new LiteRequestProcessor();
+
+        if (!empty($params->replyToId) || !empty($params->forwardFromId)) {
+            $msg = $lrp->executeRequest('GetMessage', (object) array(
+                'id' => !empty($params->replyToId) ?
+                    $params->replyToId : $params->forwardFromId
+            ));
+        } else {
+            $msg = null; // compose new mail, not reply/forward
+        }
+
+        $lrp->executeRequest('SaveMessage', (object) array(
+            'subject' => $params->subject,
+            'body' => $this->prepareMessageBody($params, $msg),
+            'to' => $params->addrTo,
+            'cc' => $params->addrCc,
+            'bcc' => $params->addrBcc,
+            'isImportant' => isset($params->important) ? '1' : '0',
+            'attachs' => empty($upFiles) && (!is_null($msg) && !$msg->has_attachment) ? '' : $this->formatAllAttachs($params, $msg, $upFiles),
+            'replyToId' => $params->replyToId,
+            'forwardFromId' => $params->forwardFromId,
+            'origDraftId' => ''
+        ));
+
+        Dispatcher::processRequest('Core.ShowFeedback', (object) array(
+            'typeMsg' => ShowFeedback::MSG_SUCCESS,
+            'message' => 'Mensagem enviada com sucesso.',
+            'destinationText' => 'Voltar para ' . $params->folderName,
+            'destinationUrl' => (object) array(
+                'action' => 'Mail.Main',
+                'params' => array(
+                   'folderId' => $params->folderId,
+                   'page' => $params->page
+                )
+            )
+        ));
+    }
+
+    /**
+     * Formats replied/forwarded message body texts.
+     *
+     * @param  stdClass $params Request parameters.
+     * @param  stdClass $msg    Message object, if replied or forwarded.
+     * @return string           Formatted body text.
+     */
+    private function prepareMessageBody($params, $msg = null)
+    {
+        $out = '';
+
+        if ($msg !== null) {
+            $formattedDate = date('d/m/Y H:i', strtotime($msg->received));
+            if (!empty($params->replyToId) && $msg !== null) {
+                $out = '<br />Em ' . $formattedDate . ', ' .
+                    $msg->from_name . ' escreveu:' .
+                    '<blockquote>' . $msg->body->message . '<br />' .
+                    ($msg->body->quoted !== null ? $msg->body->quoted : '') .
+                    '</blockquote>';
+            } else if (!empty($params->forwardFromId) && $msg !== null) {
+                $out = '<br />-----Mensagem original-----<br />' .
+                    '<b>Assunto:</b> ' . $msg->subject . '<br />' .
+                    '<b>Remetente:</b> "' . $msg->from_name . '" &lt;' . $msg->from_email . '&gt;<br />' .
+                    '<b>Para:</b> ' . implode(', ', $msg->to) . '<br />' .
+                    (!empty($msg->cc) ? '<b>Cc:</b> ' . implode(', ', $msg->cc) . '<br />' : '') .
+                    '<b>Data:</b> ' . $formattedDate . '<br /><br />' .
+                    $msg->body->message . '<br />' .
+                    ($msg->body->quoted !== null ? $msg->body->quoted : '');
+            }
+        }
+
+        return nl2br($params->messageBody) . '<br /><br />' .
+            TineSessionRepository::getTineSession()->getAttribute('Expressomail.signature') .
+            '<br />' . $out;
+    }
+
+    /**
+     * Pushes uploaded file into Tine shadows.
+     *
+     * @param  array $upFileObj Associative array of uploaded $_FILE entry.
+     * @return stdClass         Ordinary Tine upload status.
+     */
+    private function uploadFile(array $upFileObj)
+    {
+        if (empty($upFileObj['tmp_name']) || $upFileObj['error'] !== 0) {
+            return null; // this file upload slot was not used
+        }
+
+        return json_decode(MessageUtils::uploadTempFile(
+            TineSessionRepository::getTineSession(),
+            file_get_contents($upFileObj['tmp_name']),
+            $upFileObj['name'],
+            $upFileObj['type']
+        ));
+    }
+
+    /**
+     * Formats attachments (existing and new attachments) data to be sent.
+     *
+     * @param  stdClass $params Request parameters.
+     * @param  stdClass $msg Message object, if replied or forwarded.
+     * @param  array $newUploadedFiles
+     *
+     * @return attachments data that ExpressoLite needs to save a message
+     */
+    private function formatAllAttachs($params, $msg = null, $newUploadedFiles)
+    {
+        $formattedAttchments = array();
+
+        // When msg is not null is certainly a reply/forwarding email message
+        // there may be attachments
+        if (!is_null($msg) && !empty($msg->attachments)) {
+
+            // Perhaps there may be existing attachments
+            // that should not be sent on reply/forwarding email message
+            $attachNamesToBeRemoved = $this->getAttachNamesToBeRemoved($params);
+
+            foreach ($msg->attachments as $attach) {
+                if (!in_array($attach->filename, $attachNamesToBeRemoved)) {
+                    array_push($formattedAttchments, (object) array(
+                        'name' => $attach->filename,
+                        'size' => $attach->size,
+                        'type' => $attach->{'content-type'},
+                        'partId' => $attach->partId
+                    ));
+                }
+            }
+        }
+
+        // New attchment files uploaded
+        if (!empty($newUploadedFiles)) {
+            foreach ($newUploadedFiles as $newUpFile) {
+                array_push($formattedAttchments, $newUpFile);
+            }
+        }
+
+        return json_encode($formattedAttchments);
+    }
+
+    /**
+     * Gets attachment names that should be removed.
+     * All attachments that should be removed are previously marked by a checkbox
+     * when the user is reply/forwarding a message with attachments that should not be sent.
+     *
+     * @param  stdClass $params Request parameters
+     * @return array Attachment names
+     */
+    private function getAttachNamesToBeRemoved($params)
+    {
+        if (is_null($params)) {
+            return array();
+        }
+
+        $objVarsToArray = get_object_vars($params);
+
+        $patternCheckboxAttach = '/checkAttach_/';
+        return array_intersect_key(
+            $objVarsToArray, array_flip(
+                preg_grep($patternCheckboxAttach, array_keys($objVarsToArray), 0)
+            )
+        );
+    }
+}
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/ComposeMessageTemplate.css b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/ComposeMessageTemplate.css
new file mode 100644 (file)
index 0000000..0ff6af5
--- /dev/null
@@ -0,0 +1,51 @@
+/*!
+ * Expresso Lite
+ * CSS for ComposeMessageTemplate.php template.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Fatima Tonon <fatima.tonon@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+blockquote { margin:2px 0 4px 15px; padding-left:5px; border-left:1px solid #BFD2E1; }
+
+.dialogEmail { border:none; padding:0px; margin-right:20px; }
+
+.attach {
+    display:block;
+    padding:5px 10px 12px;
+    border:1px solid #FFF;
+    background-color:#F4F4F4;
+    list-style:none;
+    margin-bottom:-0.8em;
+}
+
+.composeSign {
+    color:#003852;
+    background-color:#F4F4F4;
+    font-size:80%;
+    padding:20px 25px;
+    margin:10px 0px 20px 0px;
+}
+
+.messageText {
+    border:1px solid #CCC;
+    box-shadow:2px 2px 2px rgba(0,0,0,0.2);
+    height:250px;
+    overflow-y:auto;
+    padding:6px 6px;
+    width:calc(100% - 8px);
+}
+
+.messageText pre {
+    overflow-x:auto;
+    white-space:pre-wrap; /* css-3 */
+    white-space:-moz-pre-wrap !important; /* Mozilla, since 1999 */
+}
+
+.existingAttachsForExhibition { display: table }
+.existingAttachsForExhibition li { float: left }
+.existingAttachsForExhibition p { float: right; margin-top: 5px; font-size: 80%; }
+
+.composeFooter { margin-top:5px; display:inline-block; padding:0px; color:#003852; }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/ComposeMessageTemplate.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/ComposeMessageTemplate.php
new file mode 100644 (file)
index 0000000..382dc29
--- /dev/null
@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite Accessible
+ * Template for email composing.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />
+    <link rel="icon" type="image/png" href="../img/favicon.png" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/general.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Mail/Template/ComposeMessageTemplate.css" />
+    <title><?= $VIEW->actionText ?> mensagem - ExpressoBr Acessível</title>
+    <title>Seleção de pasta - ExpressoBr Acessível</title>
+</head>
+<body>
+
+<div id="top" name="top">
+    <div id="logomark"></div>
+    <div id="anchors" name="anchors" class="links systemLinks">
+        <nav class="contentAlign">
+            <ul>
+                <li><a href="#compose" accesskey="1">Ir para preencher campos [1]</a></li>
+                <?php IF (!EMPTY($VIEW->existingAttachments)) : ?>
+                <li><a href="#emailAttachments" accesskey="2">Ir para anexos de email [2]</a></li>
+                <?php ENDIF; ?>
+                <li><a href="<?= $VIEW->lnkBackUrl ?>" accesskey="v">Voltar para <?= $VIEW->lnkBackText ?> [v]</a></li>
+            </ul>
+        </nav>
+    </div>
+</div>
+
+<form action="<?= $VIEW->lnkSendMessageAction ?>" method="post" enctype="multipart/form-data">
+
+    <div id="compose" name="compose">
+        <h2 class="anchorsTitle"><?= $VIEW->actionText ?> mensagem</h2>
+        <div class="dialogEmail contentAlign">
+            <input type="hidden" name="folderId" value="<?= $VIEW->folderId ?>" />
+            <input type="hidden" name="folderName" value="<?= $VIEW->folderName ?>" />
+            <input type="hidden" name="page" value="<?= $VIEW->page ?>" />
+            <input type="hidden" name="replyToId" value="<?= $VIEW->replyToId ?>" />
+            <input type="hidden" name="forwardFromId" value="<?= $VIEW->forwardFromId ?>" />
+
+            <div>
+                <label for="subject">Assunto:</label>
+                <input type="text" name="subject" id="subject" pattern="(?=.*\S).{1,}" title="O email esta sem assunto" required="required" value="<?= $VIEW->subject ?>" />
+            </div>
+
+            <div>
+                <label for="addrTo">Destinatário:</label>
+                <input type="email" multiple="multiple" name="addrTo" title="Informe um endereço de email válido e utilize a virgula como separador" id="addrTo" required="required" value="<?= $VIEW->to ?>"/>
+            </div>
+
+            <div>
+                <label for="addrCc">Destinatário em cópia:</label>
+                <input type="email" multiple="multiple" name="addrCc" id="addrCc" value="<?= $VIEW->cc ?>" />
+            </div>
+
+            <div>
+                <label for="addrBcc">Destinatário em cópia oculta:</label>
+                <input type="email" multiple="multiple" name="addrBcc" id="addrBcc" />
+            </div >
+
+            <div>
+                <label for="messageBody">Mensagem:</label>
+                <textarea name="messageBody" id="messageBody"></textarea>
+            </div>
+
+            <?php IF ($VIEW->signature !== '') : ?>
+            <div class="composeSign"><?= $VIEW->signature ?></div>
+            <?php ENDIF; ?>
+
+            <?php IF ($VIEW->quotedBody !== '') : ?>
+                <label for="quotedBody">Mensagem citada:</label>
+                <div class="messageText" name="quotedBody" id="quotedBody">
+                    <?= $VIEW->quotedBody ?>
+                </div>
+            <?php ENDIF; ?>
+        </div>
+    </div>
+
+    <div id="emailAttachments" name="emailAttachments" >
+        <h2 class="anchorsTitle">Anexos</h2>
+        <?php IF (!EMPTY($VIEW->existingAttachments)) : ?>
+            <div id="attachments" name="attachments" class="links systemLinks">
+                <ul>
+                    <?php FOREACH ($VIEW->existingAttachments as $ATTACH) : ?>
+                        <div class ="existingAttachsForExhibition">
+                            <li>
+                                <a href="<?= $ATTACH->lnkDownload ?>">
+                                     Anexo <?= $ATTACH->accessibleFileName ?>
+                                     (formato <?= $ATTACH->accessibleExtension ?>, tamanho <?= $ATTACH->accessibleFileSize ?>)
+                                </a>
+                            </li>
+                            <p>
+                                <input type="checkbox"  id="checkAttach_<?php ECHO $ATTACH->filename ?>" name="checkAttach_<?php ECHO $ATTACH->filename ?>" value="<?php ECHO $ATTACH->filename?>" title="Marcar este anexo para não ser enviado" />
+                                <label for="checkAttach_<?php ECHO $ATTACH->filename ?>">Remover anexo</label>
+                            </p>
+                        </div>
+                    <? ENDFOREACH; ?>
+                </ul>
+            </div>
+        <?php ENDIF; ?>
+        <div class="contentAlign">
+            <p class="attach">
+                <label for="attach0">Anexar 1º arquivo:</label>&nbsp;
+                <input type="file" name="attach0" id="attach0" />
+            </p>
+            <p class="attach">
+                <label for="attach1">Anexar 2º arquivo:</label>&nbsp;
+                <input type="file" name="attach1" id="attach1" />
+            </p>
+            <p class="attach">
+                <label for="attach2">Anexar 3º arquivo:</label>&nbsp;
+                <input type="file" name="attach2" id="attach2" />
+            </p>
+        </div>
+    </div>
+
+    <div class="composeFooter contentAlign">
+        <p>
+            <label for="important">Esta mensagem é importante</label>
+            <input type="checkbox" name="important" id="important" title="Marcar essa mensagem como importante" />
+        </p>
+        <p>
+            <input type="submit" value="Enviar" />
+        <p>
+    </div>
+
+</form>
+
+</body>
+</html>
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/MainTemplate.css b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/MainTemplate.css
new file mode 100644 (file)
index 0000000..bb03899
--- /dev/null
@@ -0,0 +1,58 @@
+/*!
+ * Expresso Lite
+ * CSS for MainTemplate.php template.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Fatima Tonon <fatima.tonon@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+/* --- MAIN MENU --- */
+#menu ul { margin-left:-30px; margin-right:10px; }
+
+/* --- HEADLINES --- */
+#headlinesTable { width:calc(100% - 20px); }
+#headlinesTable { border:1px solid #FFF; border-collapse:collapse; }
+#headlinesTable caption { padding:5px 10px; font-weight:bold; }
+
+#headlinesTable th, td { border:2px solid #FFF; background-color:#F4F4F4; }
+#headlinesTable th { font-weight:400; font-size:110%; padding:5px; background-color:#005076; color:#FFF; }
+#headlinesTable th:first-of-type { border-top-left-radius:6px; }
+#headlinesTable th:last-of-type { border-top-right-radius:6px; }
+
+.markUnread { font-weight:600; }
+.alignCenter { text-align:center; }
+.alignLeft { padding:0px 10px; }
+.emptyHeadlines { font-size:150%; }
+
+/* --- ICONS --- */
+
+.flags {
+    background:url("../../../../img/flags.png") no-repeat;
+    display:inline-block;
+    width:16px;
+    height:16px;
+}
+.flag-important { background-position:0px -17px; }
+.flag-attach { background-position:0px 0px; }
+
+.icoAttach, .icoImportant, .icoReplied, .icoForwarded, .icoConfirm,
+    .icoSigned, .icoHigh0, .icoHigh1 {
+    width:16px;
+    height:16px;
+    display:inline-block;
+    background-image:url(../../../../img/flags.png);
+}
+
+.icoAttach { background-position:0 0; opacity:.65; }
+.icoImportant { background-position:0 -16px; opacity:.65; }
+.icoReplied { background-position:0 -32px; opacity:.65; }
+.icoForwarded { background-position:0 -48px; opacity:.65; }
+.icoConfirm { background-position:0 -64px; opacity:.65; }
+.icoSigned { background-position:0 -80px; opacity:.65; }
+.icoHigh0 { background-position:0 -96px; }
+.icoHigh1 { background-position:0 -112px; }
+
+#actions { margin-bottom: 35px }
+#pagination ul { margin-left:-30px; margin-top: 25px }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/MainTemplate.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/MainTemplate.php
new file mode 100644 (file)
index 0000000..4842eee
--- /dev/null
@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite Accessible
+ * Entry index page for mail module.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />
+    <link rel="icon" type="image/png" href="../img/favicon.png" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/general.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Mail/Template/MainTemplate.css" />
+    <title><?= $VIEW->curFolder->localName ?> - ExpressoBr Acessível</title>
+</head>
+<body>
+
+<div id="top" name="top" >
+    <div id="anchors" name="anchors" class="links systemLinks">
+        <nav class="contentAlign">
+            <ul>
+                <li><a href="#menu" accesskey="1">Ir para o menu [1]</a></li>
+                <li><a href="#headlines" accesskey="2">Ir para lista de emails [2]</a></li>
+                <?php IF ($VIEW->curFolder->totalMails > 0) : ?>
+                <li><a href="#actions" accesskey="3">Ir para ações de emails [3]</a></li>
+                <?php ENDIF; ?>
+                <?php IF ($VIEW->curFolder->totalMails > $VIEW->requestLimit) : ?>
+                <li><a href="#pagination" accesskey="4">Ir para paginação de emails [4]</a></li>
+                <?php ENDIF; ?>
+            </ul>
+        </nav>
+    </div>
+</div>
+
+<div id="menu" name="menu">
+    <h2 class="anchorsTitle">Menu</h2>
+    <div class="links systemLinks">
+        <ul>
+            <li><a href="<?= $VIEW->lnkRefreshFolder ?>" accesskey="a">Atualizar lista de emails da pasta <?= $VIEW->curFolder->localName ?> [a]</span></a></li>
+            <li><a href="<?= $VIEW->lnkChangeFolder ?>" accesskey="p">Selecionar outra pasta [p]</a></li>
+            <li><a href="<?= $VIEW->lnkComposeMessage ?>" accesskey="n">Escrever novo email [n]</span></a></li>
+            <li><a href="<?= $VIEW->lnkLogoff ?>" title="Sair do expressobr acessível" accesskey="s">Sair do sistema [s]</a></li>
+        </ul>
+    </div>
+</div>
+
+<form action="<?= $VIEW->lnkConfirmMessageAction ?>" method="post">
+<div id="headlines" name="headlines">
+    <h2 class="anchorsTitle">Lista de emails</h2>
+    <?php IF ($VIEW->curFolder->totalMails > 0) : ?>
+        <table id="headlinesTable" border="1" class="clickableCell contentAlign">
+            <caption>
+                A pasta <?= $VIEW->curFolder->localName ?> contém <?= $VIEW->curFolder->totalMails ?> emails,
+                <?php IF ($VIEW->curFolder->unreadMails > 0) : ?>
+                    sendo <?= $VIEW->curFolder->unreadMails ?> não lido,
+                <?php ENDIF; ?>
+                listando de <?= $VIEW->start ?> a <?= $VIEW->limit ?>.
+            </caption>
+            <thead>
+                <tr>
+                    <th id="id">Número</th>
+                    <th id="senderSubject">Remetente / Assunto</th>
+                    <th id="date">Data</th>
+                    <th id="observations">Observações</th>
+                    <th id="mark" aria-hidden="true">Marcar</th>
+                </tr>
+            </thead>
+            <tbody>
+                <?php
+                $SEQ =  $VIEW->start;
+                FOREACH ($VIEW->headlines AS $HEADLINE) :
+                ?>
+                <tr class="<?= $HEADLINE->unread ? 'markUnread' : '' ?>">
+                    <td headers="id" class="alignCenter">
+                        <span><?= $SEQ ?></span>
+                    </td>
+                    <td headers="senderSubject" class="alignLeft">
+                        <a href="<?= $HEADLINE->lnkOpen ?>" title="Abrir mensagem <?= $SEQ ?>">
+                            De: <span> <?= $HEADLINE->from->name ?></span>
+                            <br />
+                            <span ><?= $HEADLINE->subject ?></span>
+                        </a>
+                    </td>
+                    <td headers="date" class="alignCenter">
+                        <span><?= $HEADLINE->received ?></span>
+                    </td>
+                    <td headers="observations" class="alignLeft">
+                        <?php IF ($HEADLINE->hasAttachment) : ?>
+                        <div class="flags flag-attach" title="Este email contém anexo">&nbsp;</div>
+                        <?php ENDIF; ?>
+                        <?php IF ($HEADLINE->important) : ?>
+                        <div class="flags flag-important" title="O remetente deste email o marcou como importante">&nbsp;</div>
+                        <?php ENDIF; ?>
+                    </td>
+                    <td headers="mark" class="alignCenter">
+                        <input type="checkbox" title="mensagem <?php ECHO $SEQ ?>" id="check_<?php ECHO $SEQ ?>" name="check_<?php ECHO $SEQ ?>" value="<?= $HEADLINE->id ?>" >
+                    </td>
+                </tr>
+                <?php ++$SEQ; ?>
+                <?php ENDFOREACH; ?>
+            </tbody>
+        </table>
+    <?php ELSE : ?>
+        <p class="alignCenter emptyHeadlines">A pasta <?= $VIEW->curFolder->localName ?> está vazia.</p>
+    <?php ENDIF;?>
+</div>
+
+<?php IF ($VIEW->curFolder->totalMails > 0) : ?>
+<div id="actions" name="actions">
+    <h2 class="anchorsTitle">Ações</h2>
+    <div class="contentAlign">
+        <button type="submit" name="actionProcess" value="<?= $VIEW->action_mark_unread ?>">Marcar como não lido</button>
+    </div>
+</div>
+<?php ENDIF;?>
+</form>
+
+<?php IF ($VIEW->curFolder->totalMails > $VIEW->requestLimit) : ?>
+<div id="pagination" name="pagination">
+    <h2 class="anchorsTitle">Paginação</h2>
+    <div class="links linkAsButton">
+        <ul>
+            <?php IF ($VIEW->page > 1) : ?>
+                <li><a href="<?= $VIEW->lnkPrevPage ?>">Página Anterior</a></li>
+            <?php ENDIF; ?>
+            <?php IF ($VIEW->page * $VIEW->requestLimit < $VIEW->curFolder->totalMails) : ?>
+                <li><a href="<?= $VIEW->lnkNextPage ?>">Próxima Página</a></li>
+            <?php ENDIF; ?>
+        </ul>
+    </div>
+</div>
+<?php ENDIF; ?>
+
+</body>
+</html>
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenFolderTemplate.css b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenFolderTemplate.css
new file mode 100644 (file)
index 0000000..ecc161c
--- /dev/null
@@ -0,0 +1,11 @@
+/*!
+ * Expresso Lite
+ * CSS for OpenFolderTemplate.css template.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Fatima Tonon <fatima.tonon@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+#folders ul { margin-left:-30px; margin-right:10px; }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenFolderTemplate.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenFolderTemplate.php
new file mode 100644 (file)
index 0000000..353572a
--- /dev/null
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite Accessible
+ * Entry index page for mail module and change messages to another folder.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Edgar de Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+-->
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />
+    <link rel="icon" type="image/png" href="../img/favicon.png" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/general.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Mail/Template/OpenFolderTemplate.css" />
+    <title>Seleção de pasta - ExpressoBr Acessível</title>
+</head>
+<body>
+
+<div id="top" name="top">
+    <div id="logomark"></div>
+    <div id="anchors" name="anchors" class="links systemLinks">
+        <nav class="contentAlign">
+            <ul>
+                <li><a href="#folders" accesskey="1">Ir para listagem de pastas [1]</a></li>
+                <?php IF ($VIEW->isMsgBeingMoved) : ?>
+                    <li><a href="<?= $VIEW->lnkRefreshMessage ?>" accesskey="m">Voltar para mensagem de origem [m]</a></li>
+                <?php ENDIF; ?>
+                <li><a href="<?= $VIEW->lnkRefreshFolder ?>" accesskey="v">Voltar para <?= $VIEW->folderName ?> [v]</a></li>
+            </ul>
+        </nav>
+    </div>
+</div>
+
+<div id="folders" name="folders">
+<h2 class="anchorsTitle">Pastas</h2>
+    <div class="links systemLinks">
+        <ul >
+            <?php FOREACH ($VIEW->folders AS $FOLDER) : ?>
+                <li><a href="<?= $FOLDER->lnkOpenFolder ?> " title="<?= $FOLDER->title ?>"><?= $FOLDER->localName ?></a></li>
+            <?php ENDFOREACH ?>
+        </ul>
+    </div>
+</div>
+
+</body>
+</html>
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenMessageTemplate.css b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenMessageTemplate.css
new file mode 100644 (file)
index 0000000..883f7e1
--- /dev/null
@@ -0,0 +1,25 @@
+/*!
+ * Expresso Lite
+ * CSS for OpenMessageTemplate.php template.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Fatima Tonon <fatima.tonon@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+blockquote { margin:2px 0 4px 14px; padding-left:5px; border-left:1px solid #BFD2E1; }
+
+.fieldName { font-weight:bold; }
+.attachName { font-weight:600; }
+
+#composePanelBody { border:1px solid #CCC; padding:6px 6px; }
+#composePanelBody pre {
+    overflow-x:auto;
+    white-space:pre-wrap; /* css-3 */
+    white-space:-moz-pre-wrap !important; /* Mozilla, since 1999 */
+}
+
+#emailActions ul { margin-left:-30px; }
+
+#emailAttachments ul { margin-left:-30px; }
diff --git a/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenMessageTemplate.php b/clients/ExpressoLite/src/accessible/Accessible/Mail/Template/OpenMessageTemplate.php
new file mode 100644 (file)
index 0000000..31029bc
--- /dev/null
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite Accessible
+ * Displays an email message.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Diogo Santos <diogo.santos@serpro.gov.br>
+ * @author    Edgar Lucca <edgar.lucca@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+-->
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1" />
+    <link rel="icon" type="image/png" href="../img/favicon.png" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Core/Template/general.css" />
+    <link type="text/css" rel="stylesheet" href="./Accessible/Mail/Template/OpenMessageTemplate.css" />
+    <title>Leitura de mensagem - ExpressoBr Acessível</title>
+</head>
+<body>
+
+<div id="top" name="top">
+    <div id="anchors" name="anchors" class="links systemLinks">
+        <nav class="contentAlign">
+            <ul>
+                <li><a href="#emailHeader" accesskey="1">Ir para o cabeçalho do email [1]</a></li>
+                <li><a href="#emailContent" accesskey="2">Ir para o corpo da mensagem [2]</a></li>
+                <?php IF (count($VIEW->attachmentsForExhibition) > 0) : ?>
+                <li><a href="#emailAttachments" accesskey="3">Ir para anexos de email [3]</a></li>
+                <?php ENDIF?>
+                <li><a href="#emailActions" accesskey="4">Ir para ações de email [4]</a></li>
+                <li><a href="<?= $VIEW->lnkBack ?>" accesskey="v">Voltar para <?= $VIEW->folderName?> [v]</a></li>
+            </ul>
+        </nav>
+    </div>
+</div>
+
+<div id="emailHeader" name="emailHeader">
+    <h2 class="anchorsTitle">Cabeçalho</h2>
+    <div class="contentAlign">
+        <div><span class="fieldName">Assunto:</span> <?= $VIEW->message->subject ?></div>
+
+        <div><span class="fieldName">Remetente:</span> <?= $VIEW->message->from_name ?> (<?= $VIEW->message->from_email ?>)</div>
+
+        <div><span class="fieldName">Data:</span> <?= $VIEW->formattedDate ?></div>
+
+        <?php IF (!EMPTY($VIEW->message->to[0])) : ?>
+            <div><span class="fieldName">Destinatário:</span> <?= implode(', ', $VIEW->message->to) ?></div>
+        <?php ENDIF; ?>
+
+        <?php IF (!EMPTY($VIEW->message->cc[0])) : ?>
+            <div><span class="fieldName">Com cópia para:</span> <?= implode(', ', $VIEW->message->cc) ?></div>
+        <?php ENDIF; ?>
+
+        <?php IF (!EMPTY($VIEW->message->bcc[0])) : ?>
+            <div><span class="fieldName">Com cópia oculta para:</span> <?= implode(', ', $VIEW->message->bcc) ?></div>
+        <?php ENDIF; ?>
+
+        <?php IF ($VIEW->message->importance) : ?>
+            <div><span class="fieldName">Observação:</span> Esta mensagem foi marcada como importante.</div>
+        <?php ENDIF; ?>
+        <?php IF ($VIEW->message->has_attachment) : ?>
+            <div class="onlyForScreenReaders" ><span class="fieldName">Observação:</span> Esta mensagem possui anexo.</div>
+        <?php ENDIF; ?>
+    </div>
+</div>
+
+<div id="emailContent" name="emailContent">
+    <h2 class="anchorsTitle">Mensagem</h2>
+    <div id="composePanelBody" name="composePanelBody" class="contentAlign">
+        <?= $VIEW->message->body->message ?>
+        <br/><br/>
+        <blockquote><?= $VIEW->message->body->quoted ?></blockquote>
+    </div>
+</div>
+
+<?php IF ($VIEW->message->has_attachment) : ?>
+<div id="emailAttachments" name="emailAttachments" >
+    <h2 class="anchorsTitle">Anexos</h2>
+    <div id="attachments" name="attachments" class="links systemLinks">
+        <ul>
+            <?php FOREACH ($VIEW->attachmentsForExhibition as $ATTACH) : ?>
+                <li>
+                    <a href="<?= $ATTACH->lnkDownload ?>">
+                         Abrir anexo <?= $ATTACH->accessibleFileName ?>
+                         (formato <?= $ATTACH->accessibleExtension ?>, tamanho <?= $ATTACH->accessibleFileSize ?>)
+                    </a>
+                </li>
+            <? ENDFOREACH; ?>
+        </ul>
+    </div>
+</div>
+<?php ENDIF; ?>
+
+<div id="emailActions" name="emailActions">
+    <h2 class="anchorsTitle">Ações</h2>
+    <div class="links linkAsButton">
+        <ul>
+            <li><a href="<?= $VIEW->lnkReply ?>">Responder</a></li>
+            <li><a href="<?= $VIEW->lnkReplyAll ?>">Responder a todos</a></li>
+            <li><a href="<?= $VIEW->lnkForward ?>">Encaminhar</a></li>
+            <li><a href="<?= $VIEW->lnkMark ?>">Marcar como não lida</a></li>
+            <li><a href="<?= $VIEW->lnkDelete ?>">Apagar</a></li>
+            <li><a href="<?= $VIEW->lnkMoveMsgToFolder ?>">Mover mensagem</a></li>
+        </ul>
+    <div>
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/clients/ExpressoLite/src/accessible/bootstrap.php b/clients/ExpressoLite/src/accessible/bootstrap.php
new file mode 100644 (file)
index 0000000..fe2c4f2
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Bootstrap to be added in accessible module. Adds everything
+ * included in the API bootstrap plus an extra class loader for
+ * the accessible module classes.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+require (dirname(__FILE__) . '/../api/bootstrap.php');
+
+$loader = new SplClassLoader('Accessible', dirname(__FILE__));
+$loader->register();
diff --git a/clients/ExpressoLite/src/accessible/index.php b/clients/ExpressoLite/src/accessible/index.php
new file mode 100644 (file)
index 0000000..3f0d642
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Expresso Lite Accessible
+ * Instantiates the dispatcher.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+require_once (dirname(__FILE__) . '/bootstrap.php');
+
+use Accessible\Dispatcher;
+
+Dispatcher::processRawHttpRequest($_REQUEST);
diff --git a/clients/ExpressoLite/src/addressbook/WidgetCatalogMenu.css b/clients/ExpressoLite/src/addressbook/WidgetCatalogMenu.css
new file mode 100644 (file)
index 0000000..c8ab9d7
--- /dev/null
@@ -0,0 +1,25 @@
+/*!
+ * Expresso Lite
+ * Styles for WidgetCatalogMenu object.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+#WidgetCatalogMenu_list {padding:0; list-style-type:none;}
+
+#WidgetCatalogMenu_list li {cursor:pointer; width: 100%; line-height: 32px;}
+
+@media (min-width:1025px) { /* desktop */
+    #WidgetCatalogMenu_list li {padding: 0px 4px;}
+}
+
+@media (max-width:1024px) { /* mobile */
+    #WidgetCatalogMenu_list li {padding: 8px 4px;}
+}
+
+#WidgetCatalogMenu_list li.selected {background-color:#D9E8FB;}
+
+#WidgetCatalogMenu_list li:hover { background-color:#F5F5F5; }
diff --git a/clients/ExpressoLite/src/addressbook/WidgetCatalogMenu.js b/clients/ExpressoLite/src/addressbook/WidgetCatalogMenu.js
new file mode 100644 (file)
index 0000000..3199cbf
--- /dev/null
@@ -0,0 +1,56 @@
+/*!
+ * Expresso Lite
+ * Left menu of addressbook module.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+define(['jquery',
+    'common-js/App'
+],
+function($, App) {
+
+    App.LoadCss('addressbook/WidgetCatalogMenu.css');
+
+    return function(options) {
+        var userOpts = $.extend({
+            $parentContainer: null
+        }, options);
+
+        var THIS = this;
+        var $ul = $(document.createElement('ul')).attr('id', 'WidgetCatalogMenu_list').appendTo(options.$parentContainer);
+        var $selectedLi = null;
+
+        function selectLi($li) {
+            if ($selectedLi != null) {
+                $selectedLi.removeClass('selected');
+            }
+            $selectedLi = $li;
+            $selectedLi.addClass('selected');
+        }
+
+        THIS.addOption = function (label, callback) {
+            var $li = $(document.createElement('li'));
+
+            if ($selectedLi == null) {
+                selectLi($li);
+            }
+            $li.html(label);
+            $li.data('callback', callback);
+
+            $li.on('click', function () {
+                var $this = $(this);
+                if (!$this.hasClass('selected')) {
+                    selectLi($this);
+                }
+                $this.data('callback')(); //invokes callback associated to <li>
+            });
+
+            $li.appendTo($ul);
+            return THIS;
+        }
+    }
+});
diff --git a/clients/ExpressoLite/src/addressbook/WidgetContactDetails.css b/clients/ExpressoLite/src/addressbook/WidgetContactDetails.css
new file mode 100644 (file)
index 0000000..388b0fa
--- /dev/null
@@ -0,0 +1,170 @@
+/*!
+ * Expresso Lite
+ * Styles for WidgetContactDetails object.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+#WidgetContactDetails_templates {
+    display: none;
+}
+
+@media (min-width:1025px) { /* desktop */
+    #WidgetContactDetails_mainDiv {
+        padding: 20px;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    #WidgetContactDetails_mainDiv {
+        padding: 20px 10px;
+    }
+}
+
+#WidgetContactDetails_contentTop > * {
+    display: inline-block;
+    vertical-align: middle;
+}
+
+#WidgetContactDetails_mugshotDiv {
+    position: relative;
+    width: 100px;
+    text-align: center;
+    padding: 0x;
+}
+
+#WidgetContactDetails_mugshotThrobber {
+    position: absolute;
+    top: 48px;
+    left: 42px;
+}
+
+@media (min-width:1025px) { /* desktop */
+    #WidgetContactDetails_mainInfoDiv {
+        margin-left: 20px;
+        width: 280px;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    #WidgetContactDetails_mainInfoDiv {
+        margin-left: 10px;
+        width: calc(100% - 130px);
+    }
+}
+
+.WidgetContactDetails_name {
+    width: 100%;
+    font-size: large;
+    font-weight: bold;
+}
+
+@media (max-width:1024px) { /* mobile */
+    .WidgetContactDetails_name {
+        padding-bottom: 16px;
+    }
+}
+
+.WidgetContactDetails_email {
+    font-style: italic;
+    background: url("../img/e_mail.png") no-repeat;
+    padding-left: 20px;
+    font-size: small;
+}
+
+@media (min-width:1025px) { /* desktop */
+    .WidgetContactDetails_email {
+        padding-bottom: 12px;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    .WidgetContactDetails_email {
+        padding-bottom:8px;
+    }
+}
+
+.WidgetContactDetails_phonesDiv {
+    display: block;
+}
+
+a.phoneNumber:hover, a.phoneNumber:visited, a.phoneNumber:link, a.phoneNumber:active {
+    text-decoration: none;
+    color: #1a1a1a;
+}
+
+.WidgetContactDetails_phonesDiv * {
+    display: inline-block;
+    padding-right: 20px;
+}
+
+.WidgetContactDetails_phone {
+    background: url("../img/phone.png") no-repeat;
+    padding-left: 20px;
+}
+
+.WidgetContactDetails_phone:empty {
+    display: none;
+}
+
+@media (max-width:1024px) { /* mobile */
+    .WidgetContactDetails_phone {
+        padding-bottom:8px;
+    }
+}
+
+.WidgetContactDetails_mobile {
+    background: url("../img/mobile.png") no-repeat;
+    padding-left: 20px;
+}
+
+#WidgetContactDetails_mainDiv hr{
+    margin: 20px;
+    border: 1px solid #DDD;
+}
+
+#WidgetContactDetails_throbberDiv {
+    width: 100%;
+    text-align: center;
+}
+
+@media (min-width:1025px) { /* desktop */
+    #WidgetContactDetails_otherFieldsDiv {
+        display: block;
+        width: 100%;
+        max-height: calc(100% - 180px);
+        overflow-y: auto;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    #WidgetContactDetails_otherFieldsDiv {
+        display: table;
+        width: 100%;
+    }
+}
+
+#WidgetContactDetails_otherFieldsDiv > div{
+    display: table-row;
+}
+
+#WidgetContactDetails_otherFieldsDiv > div > *{
+    display: table-cell;
+}
+
+.WidgetContactDetails_otherFieldLabel {
+    width: 35%;
+    padding-right: 12px;
+    text-align: right;
+    font-weight: bold;
+    color: #555;
+}
+
+.WidgetContactDetails_otherFieldValue {
+    width: 100%;
+    border: none;
+    background: transparent;
+}
diff --git a/clients/ExpressoLite/src/addressbook/WidgetContactDetails.html b/clients/ExpressoLite/src/addressbook/WidgetContactDetails.html
new file mode 100644 (file)
index 0000000..b95ac11
--- /dev/null
@@ -0,0 +1,37 @@
+<!--
+ * Expresso Lite
+ * HTML template for WidgetContactDetails
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<div id="WidgetContactDetails_templates">
+    <div id="WidgetContactDetails_mainDiv">
+        <div id="WidgetContactDetails_contentTop">
+            <div id="WidgetContactDetails_mugshotDiv">
+                <img id="WidgetContactDetails_mugshot" src="../img/person-generic.gif">
+                <img id="WidgetContactDetails_mugshotThrobber" src="../img/chromiumthrobber.svg"/>
+            </div>
+            <div id="WidgetContactDetails_mainInfoDiv">
+                <div class="WidgetContactDetails_name">Contact Name</div>
+                <div class="WidgetContactDetails_email">contact@email.com</div>
+                <div class="WidgetContactDetails_phonesDiv">
+                    <div class="WidgetContactDetails_phone">3333-3333</div>
+                    <div class="WidgetContactDetails_mobile">9999-9999</div>
+                </div>
+            </div>
+        </div>
+        <hr>
+        <div id="WidgetContactDetails_throbberDiv">
+            <img id="WidgetContactDetails_throbberImg" src="../img/chromiumthrobber.svg"/>
+        </div>
+        <div id="WidgetContactDetails_otherFieldsDiv">
+            <div class="WidgetContactDetails_otherFieldRow">
+                <div class="WidgetContactDetails_otherFieldLabel">Field name:</div>
+                <input class="WidgetContactDetails_otherFieldValue" value="Field value">
+            </div>
+        </div>
+    </div>
+</div>
diff --git a/clients/ExpressoLite/src/addressbook/WidgetContactDetails.js b/clients/ExpressoLite/src/addressbook/WidgetContactDetails.js
new file mode 100644 (file)
index 0000000..924ccff
--- /dev/null
@@ -0,0 +1,144 @@
+/*!
+ * Expresso Lite
+ * Widget that shows a div with all contact details.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+define(['jquery',
+    'common-js/App',
+    'common-js/Contacts'
+],
+function($, App,Contacts) {
+    App.LoadCss('addressbook/WidgetContactDetails.css');
+    return function(options) {
+        var userOpts = $.extend({
+            $parentContainer: null
+        }, options);
+
+        var THIS = this;
+
+        var $mainDiv;
+        var $fieldTemplate;
+        var $throbber;
+        //all these will be set by loadHtmlTemplates
+
+        var minimizedFrame = null;
+        var $contactListItemDiv = null;
+        //these two will be set by showDetails
+
+        var maximizedFrame = null;
+
+        var otherFields = {
+            'salutation': 'Saudação',
+            'n_prefix': 'Título',
+            'org_name': 'Empresa',
+            'org_unit': 'Unidade',
+            'bday': 'Aniversário',
+            'adr_one_street': 'Rua',
+            'adr_one_street2': 'Rua 2',
+            'adr_one_region': 'Estado',
+            'adr_one_postalcode': 'Código Postal',
+            'adr_one_locality': 'Cidade',
+            'adr_one_country': 'Páis'
+        };
+
+        function hex2bin(hex) {
+            var bytes = [];
+            for(var i = 0; i < hex.length - 1; i += 2) {
+                bytes.push(parseInt(hex.substr(i, 2), 16));
+            }
+            return String.fromCharCode.apply(String, bytes);
+        }
+
+        function showContactDetailsFormAdditionalInfo(fullContact) {
+            if (fullContact.mugshot == '') {
+                $mainDiv.find('#WidgetContactDetails_mugshot').attr('src', '../img/person-generic.gif');
+            } else {
+                $mainDiv.find('#WidgetContactDetails_mugshot').attr('src', 'data:image/jpeg;base64,' + hex2bin(fullContact.mugshot));
+            }
+            $mainDiv.find('#WidgetContactDetails_mugshotThrobber').hide();
+            $mainDiv.find('#WidgetContactDetails_mugshot').css('visibility', '').hide().fadeIn(200);
+
+            $throbber.hide();
+
+            $otherFieldsDiv.hide();
+            for (var fieldId in otherFields) {
+                var fieldIsNotEmpty =
+                    fullContact.otherFields[fieldId] != undefined &&
+                    fullContact.otherFields[fieldId].trim() != '';
+
+                if (fieldIsNotEmpty) {
+                    $newField = $fieldTemplate.clone();
+                    $newField.find('.WidgetContactDetails_otherFieldLabel').html(otherFields[fieldId] + ':');
+                    $newField.find('.WidgetContactDetails_otherFieldValue').val(fullContact.otherFields[fieldId]);
+                    $newField.appendTo($otherFieldsDiv);
+                }
+            }
+            $otherFieldsDiv.fadeIn(200);
+        }
+
+        function createTelLink(number) {
+            if (!number) {
+                return '';
+            } else if (App.IsPhone()) {
+                return number ? '<a class="phoneNumber" href="tel:' + number + '">' + number + '</a>' : '';
+            } else {
+                return number;
+            }
+        }
+
+        function showContactDetailsFormTopInfo(contact) {
+            $mainDiv.find('.WidgetContactDetails_name').html(contact.name);
+            $mainDiv.find('.WidgetContactDetails_email').html(contact.email);
+            $mainDiv.find('.WidgetContactDetails_phone').html(createTelLink(contact.phone));
+            $mainDiv.find('.WidgetContactDetails_mobile').html(createTelLink(contact.mobile));
+
+            $mainDiv.find('#WidgetContactDetails_mugshot').css('visibility', 'hidden');
+            $mainDiv.find('#WidgetContactDetails_mugshotThrobber').show();
+
+            $otherFieldsDiv = $('#WidgetContactDetails_otherFieldsDiv').empty();
+            $throbber.show();
+            $mainDiv.show();
+        }
+
+        function loadHtmlTemplates() {
+            var defer = $.Deferred();
+            $.get('WidgetContactDetails.html', function(elems) {
+                $(document.body).append($(elems));
+                defer.resolve();
+            });
+            return defer.promise();
+        }
+
+        THIS.showDetails = function (contact) {
+            $mainDiv.show();
+            showContactDetailsFormTopInfo(contact);
+            App.Post('getContact', {id: contact.id})
+            .done(function(fullContact) {
+                showContactDetailsFormAdditionalInfo(fullContact);
+            });
+        }
+
+        THIS.load = function() {
+            return loadHtmlTemplates()
+                .done(function () {
+                    $mainDiv =
+                        $('#WidgetContactDetails_mainDiv')
+                        .hide()
+                        .appendTo(userOpts.$parentContainer);
+
+                    $throbber =
+                        $('#WidgetContactDetails_throbberDiv')
+                        .hide();
+
+                    $fieldTemplate =
+                        $mainDiv.find('.WidgetContactDetails_otherFieldRow')
+                        .detach();
+                });
+        }
+    }
+});
diff --git a/clients/ExpressoLite/src/addressbook/WidgetContactList.css b/clients/ExpressoLite/src/addressbook/WidgetContactList.css
new file mode 100644 (file)
index 0000000..dbb953c
--- /dev/null
@@ -0,0 +1,126 @@
+/*!
+ * Expresso Lite
+ * Styles for WidgetContactList object.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+#WidgetContactList_templates {
+    display: none;
+}
+
+.WidgetContactList_letterSeparator {
+    font-size: x-large;
+    font-weight: bolder;
+    color: #005A97;
+    padding: 8px 24px 8px 8px;
+    text-align: left;
+    border-bottom: 1px solid #CCC;
+}
+
+@media (max-width:1024px) { /* mobile */
+    .WidgetContactList_letterSeparator {
+        background-color: #F0F0F0;
+    }
+}
+
+@media (min-width:1025px) { /* desktop */
+    .WidgetContactList_letterSeparator {
+        margin: 20px 0px 4px 0px;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    #WidgetContactList_mainSection {
+        height: 100%;
+        overflow-y: auto;
+        padding-bottom: 30px;
+    }
+
+    .WidgetContactList_item {
+        padding: 12px 6px;
+        border-bottom: 1px solid #DDD;
+        cursor: pointer;
+    }
+
+    .WidgetContactList_name {
+        font-size: large;
+    }
+
+    .WidgetContactList_email {
+        display: none;
+    }
+
+    .WidgetContactList_phonesDiv {
+        display: none;
+    }
+}
+
+@media (min-width:1025px) { /* desktop */
+    #WidgetContactList_mainSection {
+        padding-bottom: 30px;
+    }
+
+    .WidgetContactList_item {
+        display: inline-block;
+        vertical-align: middle;
+        padding: 0px;
+        width: 320px;
+        background-color: #E9E9E9;
+        border: 30px solid transparent;
+        border-image: url("../img/page_ear.png") 30 / 30px repeat;
+        background-clip: padding-box;
+        cursor: pointer;
+    }
+
+    .WidgetContactList_itemSelected {
+        background-color: #d9e8fb !important;
+        border-image: url("../img/page_ear_selected.png") 30 / 30px repeat;
+    }
+
+    .WidgetContactList_name {
+        width: 100%;
+        font-weight: bold;
+        padding-bottom: 6px;
+    }
+
+    .WidgetContactList_email {
+        font-style: italic;
+        background: url("../img/e_mail.png") no-repeat;
+        padding-bottom: 3px;
+        padding-left: 20px;
+        font-size: small;
+    }
+
+    .WidgetContactList_phonesDiv {
+        display: block;
+    }
+
+    .WidgetContactList_phonesDiv * {
+        display: inline-block;
+        padding-right: 20px;
+    }
+
+    .WidgetContactList_phone {
+        background: url("../img/phone.png") no-repeat;
+        padding-left: 20px;
+    }
+
+    .WidgetContactList_mobile {
+        background: url("../img/mobile.png") no-repeat;
+        padding-left: 20px;
+    }
+}
+
+#WidgetContactList_footer {
+    text-align: center;
+    padding: 12px 0;
+}
+
+#WidgetContactList_loadedCountSpan{
+    color: #888;
+    padding-bottom: 10px;
+}
diff --git a/clients/ExpressoLite/src/addressbook/WidgetContactList.html b/clients/ExpressoLite/src/addressbook/WidgetContactList.html
new file mode 100644 (file)
index 0000000..e0c3158
--- /dev/null
@@ -0,0 +1,26 @@
+<!--
+ * Expresso Lite
+ * HTML template for WidgetContactList
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<div id="WidgetContactList_templates">
+    <div id="WidgetContactList_mainSection">
+        <div class="WidgetContactList_item">
+            <div class="WidgetContactList_name">Contact Name</div>
+            <div class="WidgetContactList_email">contact@email.com</div>
+            <div class="WidgetContactList_phonesDiv">
+                <div class="WidgetContactList_phone">3333-3333</div>
+                <div class="WidgetContactList_mobile">9999-9999</div>
+            </div>
+        </div>
+    </div>
+    <footer id="WidgetContactList_footer">
+        <span id="WidgetContactList_loadedCountSpan">X contatos carregados</span> &nbsp;
+        <input type="button"id="WidgetContactList_loadMoreButton" value="carregar mais" />
+    </footer>
+    <img id="WidgetContactList_throbber" src="../img/chromiumthrobber.svg">
+</div>
diff --git a/clients/ExpressoLite/src/addressbook/WidgetContactList.js b/clients/ExpressoLite/src/addressbook/WidgetContactList.js
new file mode 100644 (file)
index 0000000..c794137
--- /dev/null
@@ -0,0 +1,325 @@
+/*!
+ * Expresso Lite
+ * Widget that shows a list of contacts.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+define(['jquery',
+    'common-js/App',
+    'addressbook/WidgetLetterIndex'
+],
+function($, App, WidgetLetterIndex) {
+    App.LoadCss('addressbook/WidgetContactList.css');
+
+    return function(options) {
+        var userOpts = $.extend({
+            $parentContainer: null
+        }, options);
+
+        var THIS = this;
+        var catalogs = null;
+
+        var currentSearchFilter = {
+                catalog: null,
+                query: '',
+                pageStart: 0
+        };
+
+        var $mainSection;
+        var $itemTemplate;
+        var $pagingFooter;
+        var $throbber;
+
+        var lastInitialLetter = ''; //the last initial letter that was displayed
+
+        var onItemClickCB = function () {};
+
+        function createWidgetLetterIndex() {
+            var $letterIndexAside =
+                $(document.createElement('aside'))
+                .attr('id', 'letterIndexAside')
+                .appendTo(userOpts.$parentContainer);
+
+            var widget = new WidgetLetterIndex({$parentContainer: $letterIndexAside});
+
+            widget.onLetterSelect(function(letter) {
+                THIS.goToLetter(letter);
+            });
+
+            return widget;
+        }
+
+        function builLetterSeparatorDiv(initialLetter) {
+            var $div = $(document.createElement('div'))
+                .attr('id', 'letterSeparator_' + (initialLetter == '#' ? 'number' : initialLetter))
+                .addClass('WidgetContactList_letterSeparator')
+                .html(initialLetter);
+            return $div;
+        }
+
+        function buildContactDiv(contact) {
+            var $div = $itemTemplate.clone();
+            $div.find('.WidgetContactList_name').html(contact.name);
+            $div.find('.WidgetContactList_email').html(contact.email);
+
+            if (contact.phone || contact.mobile) {
+                $div.find('.WidgetContactList_phone').html(contact.phone);
+                $div.find('.WidgetContactList_mobile').html(contact.mobile);
+            } else {
+                $div.find('.WidgetContactList_phonesDiv').hide();
+            }
+
+            $div.data('contact', contact);
+            return $div;
+        }
+
+        function addContactsToList(contacts) {
+            var divs = [];
+
+            for (var i=0; i<contacts.length; i++) {
+                var contact = contacts[i];
+                var initialLetter = contact.name.charAt(0).toUpperCase();
+                if (initialLetter < 'A' || initialLetter > 'Z') { //TODO: this does not handle accents
+                    initialLetter = '#';
+                }
+
+                if (initialLetter !== lastInitialLetter) {
+                    divs.push(builLetterSeparatorDiv(initialLetter));
+                }
+
+                var $div = buildContactDiv(contact);
+                $div.on('click', function () {
+                    THIS.unselectCurrentItem();
+                    $(this).addClass('WidgetContactList_itemSelected');
+                    onItemClickCB($(this).data('contact'));
+                });
+
+                divs.push($div);
+
+                lastInitialLetter = initialLetter;
+            }
+
+            $mainSection.append(divs);
+        }
+
+        function showError(message) {
+            alert(message);
+        }
+
+        function loadMoreContacts() {
+
+            if (currentSearchFilter.query.length < 3 && !currentSearchFilter.catalog.autoload) {
+                showMessage('Digite parte do nome do contato<br>(pelo menos 3 caracteres)');
+                return;
+            }
+
+            showLoadingThrobber();
+
+            App.Post('getContactsByFilter', {
+                containerPath: currentSearchFilter.catalog.containerPath,
+                query:         currentSearchFilter.query,
+                start:         currentSearchFilter.pageStart,
+                limit:         currentSearchFilter.catalog.pageLimit
+            }).done( function (result) {
+                if (result.contacts.length > 0) {
+                    currentSearchFilter.pageStart += result.contacts.length;
+                    hideMessageWithAnimation()
+                    .done(function() {
+                        $mainSection.hide();
+                        addContactsToList(result.contacts);
+
+                        if (currentSearchFilter.pageStart < result.totalCount) {
+                            showPagingFooter(result.totalCount);
+                        }
+                        $mainSection.fadeIn(150);
+                    });
+                } else {
+                    if (currentSearchFilter.pageStart == 0) {
+                        showMessage('Nenhum contato encontrado');
+                    }
+                }
+            }).fail(function (data) {
+                hideMessage();
+                showError('Ocorreu um erro ao consultar os contatos');
+            });
+        }
+
+        function showPagingFooter(totalCount) {
+
+            var howManyMore = totalCount - currentSearchFilter.pageStart;
+
+            if (howManyMore > currentSearchFilter.catalog.pageLimit) {
+                howManyMore = currentSearchFilter.catalog.pageLimit;
+            }
+
+            $pagingFooter.find('#WidgetContactList_loadedCountSpan')
+                .html(currentSearchFilter.pageStart + ' de ' + totalCount + ' carregados');
+
+            $pagingFooter.find('#WidgetContactList_loadMoreButton')
+                .val('carregar +' + howManyMore);
+
+            $pagingFooter
+                .detach() // remove it...
+                .appendTo($mainSection) // ... and then re-append it at the end
+                .on('click', function () {
+                    $mainSection.find('#WidgetContactList_footer').hide();
+                    loadMoreContacts();
+                })
+                .show();
+        }
+
+        function clearList() {
+            $mainSection.empty();
+            currentSearchFilter.pageStart = 0;
+            lastInitialLetter = '';
+        }
+
+        function showLoadingThrobber() {
+            showMessage('Carregando contatos... ', true);
+        }
+
+        function showMessage(msg, showThrobber) {
+            var $messageDiv = $mainSection.find('.message');
+
+            if ($messageDiv.length > 0) {
+                $messageDiv.empty();
+            } else {
+                $messageDiv =
+                    $(document.createElement('div'))
+                    .addClass('message')
+                    .appendTo($mainSection);
+            }
+
+            $messageDiv.html(msg);
+
+            if (showThrobber) {
+                $throbber.clone().appendTo($messageDiv);
+            }
+        }
+
+        function hideMessage() {
+            $mainSection.children('.message').remove();
+        }
+
+        function hideMessageWithAnimation() {
+            var defer = $.Deferred();
+
+            var $message = $mainSection.find('.message');
+
+            $message.animate(
+                {'margin-top': (userOpts.$parentContainer.height() - 20)+'px'},
+                200,
+                function() {
+                    $message.remove();
+                    defer.resolve();
+                }
+            );
+
+            return defer;
+        }
+
+        function changeCatalog(newCatalog) {
+            clearList();
+            currentSearchFilter.catalog = newCatalog;
+            currentSearchFilter.query='';
+            showTitle(newCatalog.title);
+            return loadMoreContacts();
+        }
+
+        function loadHtmlTemplates() {
+            var defer = $.Deferred();
+            $.get('WidgetContactList.html', function(elems) {
+                $(document.body).append($(elems));
+                defer.resolve();
+            });
+            return defer.promise();
+        }
+
+        function loadCatalogs() {
+            var defer = $.Deferred();
+
+            App.Post('getContactCatalogsCategories')
+            .done(function(result) {
+                catalogs = result;
+                defer.resolve();
+            });
+
+            return defer.promise();
+        }
+
+        function showTitle(catalogLabel) {
+            document.title = catalogLabel + ' - '+App.GetUserInfo('mailAddress')+' - ExpressoBr';
+            Cache.layout.setTitle(catalogLabel);
+        }
+
+        THIS.changeToPersonalCatalog = function() {
+            changeCatalog(catalogs.personal);
+        }
+
+        THIS.changeToCorporateCatalog = function() {
+            changeCatalog(catalogs.corporate);
+        }
+
+        THIS.changeQuery = function (newQuery) {
+            if (currentSearchFilter.query != newQuery) {
+                clearList();
+                showTitle('Busca no ' + currentSearchFilter.catalog.title);
+                currentSearchFilter.query = newQuery;
+                return loadMoreContacts();
+            }
+        }
+
+        THIS.goToLetter = function(letter) {
+            if (lastInitialLetter != '' && letter > lastInitialLetter) {
+                window.location.hash = '#contactListFooter';
+            } else {
+                window.location.hash = '#letterSeparator_' + (letter == '#' ? 'number' : letter);
+            }
+        }
+
+        THIS.unselectCurrentItem = function() {
+            $('.WidgetContactList_itemSelected').removeClass('WidgetContactList_itemSelected');
+        }
+
+        THIS.scrollToCurrentItem = function() {
+            var $item = $('.WidgetContactList_itemSelected');
+            userOpts.$parentContainer.scrollTop(
+                    $item.offset().top -
+                    $mainSection.offset().top -
+                    (userOpts.$parentContainer.height() / 2) +
+                    $item.height() + 20);
+        }
+
+        THIS.onItemClick = function(callback) {
+            onItemClickCB = callback;
+        }
+
+        THIS.load = function () {
+            var defer = $.Deferred();
+            createWidgetLetterIndex();
+
+            loadHtmlTemplates()
+            .done(function() {
+                $mainSection = $('#WidgetContactList_mainSection')
+                    .appendTo(userOpts.$parentContainer);
+
+                $itemTemplate = $mainSection.find('.WidgetContactList_item')
+                    .detach();
+
+                $pagingFooter = $('#WidgetContactList_footer').hide();
+
+                $throbber = $('#WidgetContactList_throbber');
+
+                loadCatalogs()
+                .done(function() {
+                    defer.resolve();
+                });
+            });
+            return defer.promise();
+        }
+    }
+});
diff --git a/clients/ExpressoLite/src/addressbook/WidgetLetterIndex.css b/clients/ExpressoLite/src/addressbook/WidgetLetterIndex.css
new file mode 100644 (file)
index 0000000..dab8f75
--- /dev/null
@@ -0,0 +1,68 @@
+/*!
+ * Expresso Lite
+ * Styles for WidgetLetterIndex object.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+@media (min-width:1025px) { /* desktop */
+    #letterIndexAside {
+        display: none;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    #letterIndexAside {
+        top: 0px;
+        right: 0px;
+        position: absolute;
+        height: 100%;
+        width: 20px;
+        background-color: white;
+        z-index: 1;
+        display: table;
+        padding: 0px;
+    }
+
+    #letterIndexTable {
+        height: 100%;
+        width: 100%;
+        border-collapse: collapse;
+        border: none;
+    }
+
+    #letterIndexTable tr td {
+        border-top: 1px solid #FFF;
+        font-size: x-small;
+        padding: 1px;
+        vertical-align: middle;
+        text-align: center;
+        background-color: #999;
+        color: #EEE;
+        cursor: pointer;
+    }
+
+    #letterIndexTable tr:first-child td {
+        border-top: none;
+    }
+
+    #highlightedLetterDiv {
+        position: fixed;
+        right: 0px;
+        top: 50px;
+        width: 24px;
+        height: 32px;
+        border-top: 1px solid #FFF;
+        border-bottom: 1px solid #FFF;
+        background-color: #999;
+        color: #EEE;
+        font-weight: bold;
+        line-height: 32px;
+        padding: 0px;
+        text-align: center;
+        cursor: pointer;
+    }
+}
diff --git a/clients/ExpressoLite/src/addressbook/WidgetLetterIndex.js b/clients/ExpressoLite/src/addressbook/WidgetLetterIndex.js
new file mode 100644 (file)
index 0000000..666e238
--- /dev/null
@@ -0,0 +1,138 @@
+/*!
+ * Expresso Lite
+ * Widget that an index of letters on the right side of the
+ * phone screen
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+define(['jquery',
+    'common-js/App'
+],
+function($, App) {
+    App.LoadCss('addressbook/WidgetLetterIndex.css');
+
+    return function(options) {
+        var userOpts = $.extend({
+            $parentContainer: null,
+            reducedCharsThreshold: 500
+        }, options);
+
+        var THIS = this;
+
+        var onLetterSelectCB = null;
+
+        var chars = [
+             '#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
+            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
+
+        var reducedChars = [
+            '#', 'A', 'B', '-', 'G', 'H', '-', 'M',
+            '-', 'R', 'S', '-', 'X', 'Y', 'Z'];
+
+        var displayMode; //will be set during regenerateHtmlTable();
+
+        var $table =
+            $(document.createElement('table'))
+            .attr('id', 'letterIndexTable')
+            .appendTo(userOpts.$parentContainer);
+
+        var $highlightedLetterDiv =
+            $(document.createElement('div'))
+            .attr('id', 'highlightedLetterDiv')
+            .hide()
+            .appendTo(userOpts.$parentContainer);
+
+        var tableHeight;
+
+        var selectedChar = '';
+
+        function regenerateHtmlTable() {
+            var displayedChars;
+            if ($(window).height() < userOpts.reducedCharsThreshold) {
+                displayMode = 'reduced';
+                displayedChars = reducedChars;
+            } else {
+                displayMode = 'full';
+                displayedChars = chars;
+            }
+
+            var tableHtml = '';
+            for (var i=0; i< displayedChars.length; i++) {
+                tableHtml += '<tr><td>' + displayedChars[i] + '</td></tr>';
+            }
+            return $table.html(tableHtml);
+        }
+
+        function highlightIndex(touchY) {
+            var relativePos = touchY - 50;
+
+            // keep relativePos within table bounds
+            if (relativePos < 0) {
+                relativePos = 0
+            } else if (relativePos >= tableHeight - 1) {
+                relativePos = tableHeight -1;
+            }
+
+            var letterIndexHeight = tableHeight / chars.length; //height of a single letter index (non highlighted)
+            var currIndex = Math.floor(relativePos / letterIndexHeight); //index of the letter index currently selected
+            var newChar = chars[currIndex];
+
+            if (newChar != selectedChar) {
+                $highlightedLetterDiv.css('top',
+                        50 + //page header offset
+                        currIndex * letterIndexHeight +  // y of current index
+                        ((letterIndexHeight - 32) / 2)); // adjustments to center index with highlighted div
+                                                         // (32 is highlighted div height)
+                $highlightedLetterDiv.html(newChar);
+                $highlightedLetterDiv.show();
+
+                onLetterSelectCB(newChar);
+
+                selectedChar = newChar;
+            }
+        }
+
+        THIS.onLetterSelect = function(callback) {
+            onLetterSelectCB = callback;
+        }
+
+        $table.on('touchstart', function (e) {
+           e.originalEvent.preventDefault();
+           // if you don't do this, touchmove won't work on android
+           // http://uihacker.blogspot.tw/2011/01/android-touchmove-event-bug.html
+
+           tableHeight = $table.outerHeight();
+           //calculate this beforehand to reduce processing
+
+           highlightIndex(e.originalEvent.touches[0].clientY);
+
+        });
+
+        $table.on('touchmove', function (e) {
+            highlightIndex(e.originalEvent.touches[0].clientY);
+        });
+
+        $table.on('touchend', function (e) {
+            $highlightedLetterDiv.hide();
+            selectedChar = '';
+        });
+
+        (function constructor() {
+            regenerateHtmlTable();
+
+            $(window).resize(function(){
+                if (App.IsPhone()) {
+                    if (($(window).height() < userOpts.reducedCharsThreshold && displayMode === 'full') ||
+                        ($(window).height() >= userOpts.reducedCharsThreshold && displayMode === 'reduced')) {
+                        regenerateHtmlTable();
+                    }
+                }
+            });
+        })();
+    }
+});
diff --git a/clients/ExpressoLite/src/addressbook/addressbook.css b/clients/ExpressoLite/src/addressbook/addressbook.css
new file mode 100644 (file)
index 0000000..6fddf0d
--- /dev/null
@@ -0,0 +1,45 @@
+/*!
+ * Expresso Lite
+ * Structural and decoration CSS styles of addressbook module.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+
+#mainContent {
+    width: 100%;
+    height: 100%;
+}
+
+@media (max-width:1024px) { /* mobile */
+    #mainContent {
+        overflow-y: hidden;
+    }
+}
+
+.message {
+    height: 50px;
+    padding: 10px; color : #888;
+    font-style: italic;
+    margin: 10px;
+    text-align: center;
+    color: #888;
+}
+
+#contactListSection {
+    height: 100%;
+}
+
+@media (min-width:1025px) { /* desktop */
+    #contactListSection {
+        overflow-y: scroll;
+    }
+}
+
+@media (max-width:1024px) { /* mobile */
+    #contactListSection {
+        overflow-y: hidden;
+    }
+}
diff --git a/clients/ExpressoLite/src/addressbook/addressbook.js b/clients/ExpressoLite/src/addressbook/addressbook.js
new file mode 100644 (file)
index 0000000..867216a
--- /dev/null
@@ -0,0 +1,112 @@
+/*!
+ * Expresso Lite
+ * Main script of addressbook module.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+require.config({
+    baseUrl: '..',
+    paths: { jquery: 'common-js/jquery.min' }
+});
+
+require(['jquery',
+    'common-js/App',
+    'common-js/Layout',
+    'addressbook/WidgetCatalogMenu',
+    'addressbook/WidgetContactList',
+    'addressbook/WidgetContactDetails'
+],
+function($, App, Layout, WidgetCatalogMenu, WidgetContactList, WidgetContactDetails) {
+window.Cache = {
+    layout: null,
+    widgetCatalogMenu: null,
+    widgetContactList: null,
+    widgetContactDetails: null
+};
+
+App.Ready(function() {
+    function showDetailView() {
+        if (!Cache.layout.isRightPanelVisible()) {
+            Cache.layout.setRightPanelVisible(true);
+            Cache.widgetContactList.scrollToCurrentItem();
+        }
+    }
+
+    function showListView() {
+        Cache.widgetContactList.unselectCurrentItem();
+        if (Cache.layout.isRightPanelVisible()) {
+            Cache.layout.setRightPanelVisible(false);
+        }
+    }
+
+    (function constructor() {
+        Cache.layout = new Layout({
+            userMail: App.GetUserInfo('mailAddress'),
+            $menu: $('#leftMenu'),
+            $middle: $('#contactListSection'),
+            $right: $('#contactDetailsSection')
+        });
+
+        Cache.layout
+        .onSearch(function (text){
+            showListView();
+            Cache.widgetContactList.changeQuery(text);
+        })
+        .onHideRightPanel(function () {
+            showListView();
+        })
+        .onKeepAlive(function () {
+            App.Post('checkSessionStatus');
+            // we just want to keep the session alive,
+            // so no need for onDone
+        });
+
+
+        Cache.widgetCatalogMenu = new WidgetCatalogMenu({
+            $parentContainer: $('#tipoContatoDiv')
+        });
+
+        Cache.widgetCatalogMenu
+        .addOption('Catálogo Corporativo', function () {
+            Cache.layout.setLeftMenuVisibleOnPhone(false)
+            .done(function() {
+                Cache.widgetContactList.changeToCorporateCatalog();
+            });
+        }).addOption('Catálogo Pessoal', function () {
+            Cache.layout.setLeftMenuVisibleOnPhone(false)
+            .done(function() {
+                Cache.widgetContactList.changeToPersonalCatalog();
+            });
+        });
+
+        Cache.widgetContactList = new WidgetContactList({
+            $parentContainer: $('#contactListSection')
+        });
+
+        Cache.widgetContactList.onItemClick(function(contact) {
+            showDetailView();
+            Cache.widgetContactDetails.showDetails(contact);
+        });
+
+
+        Cache.widgetContactDetails = new WidgetContactDetails({
+            $parentContainer: $('#contactDetailsSection')
+        });
+
+        Cache.widgetContactDetails.load();
+        //user shouldn't be kept waiting for this just right now
+
+        $.when(
+            Cache.widgetContactList.load(),
+            Cache.layout.load()
+        ).done(function() {
+            Cache.widgetContactList.changeToCorporateCatalog();
+        });
+    })();
+}); // App.Ready
+
+}); // require
diff --git a/clients/ExpressoLite/src/addressbook/index.html b/clients/ExpressoLite/src/addressbook/index.html
new file mode 100644 (file)
index 0000000..eb06f6b
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+ * Expresso Lite
+ * Entry index page for addressbook module.
+ *
+ * @package   Lite
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+-->
+<html>
+<head>
+    <meta charset="UTF-8"/>
+    <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1"/>
+    <link rel="icon" type="image/png" href="../img/favicon.png"/>
+    <link type="text/css" rel="stylesheet" href="../common-js/general.css"/>
+    <link rel="stylesheet" href="addressbook.css" />
+    <title>ExpressoBr</title>
+    <script src="../cordova.js"></script>
+    <script src="../common-js/require.min.js" data-main="addressbook.js"></script>
+</head>
+<body>
+    <section id="leftMenu">
+        <div id="tipoContatoDiv"></div>
+    </section>
+    <section id="contactListSection"></section>
+    <section id="contactDetailsSection"></section>
+</body>
+</html>
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/AjaxProcessor.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/AjaxProcessor.php
new file mode 100644 (file)
index 0000000..65d11ee
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * Expresso Lite
+ * AjaxProcessor parses incoming AJAX calls, redirects them to a
+ * LiteRequestProcessor and formats the response back to the client.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend;
+
+use \Exception;
+use ExpressoLite\Exception\LiteException;
+
+class AjaxProcessor {
+
+    /**
+     * Processes an AJAX request and outputs its response.
+     * $httpRequest['r'] will
+     * be considered the request name, and other $httpRequest entries the request
+     * parameters.
+     *
+     * @param $httpRequest Should
+     *            always be $_REQUEST
+     *
+     */
+    public function processHttpRequest($httpRequest) {
+        if (!isset($httpRequest ['r'])) {
+            $this->echoResult($this->createHttpError(400, 'request function [\'r\'] is not defined'));
+        } else {
+            $requestName = $httpRequest['r'];
+            $params = $this->getParamsFromHttpRequest($httpRequest);
+
+            try {
+                $liteRequestProcessor = new LiteRequestProcessor();
+                $result = $liteRequestProcessor->executeRequest($requestName,$params);
+            } catch(LiteException $le) {
+                $result = $this->createHttpError($le->getHttpCode(), $le->getMessage());
+            } catch(Exception $e) {
+                $msg = "Error executing $requestName. Message: "  . $e->getMessage();
+                $result = $this->createHttpError(500, $msg);
+                error_log($msg); // TODO: improve exception logging
+                // Important: DO NOT PRINT THE STACK TRACE HERE, as it may include
+                // sensible user information (password, for instance)
+            }
+
+            $this->echoResult($result);
+        }
+    }
+
+    /**
+     * Filters the request name ($httpRequest['r']) to isolate the request parameters.
+     *
+     * @param $httpRequest Should
+     *            always be $_REQUEST
+     *
+     * @return An indexed array with all request parameters (but not the request name).
+     *
+     */
+    private function getParamsFromHttpRequest($httpRequest) {
+        $params = array ();
+
+        foreach ( $httpRequest as $key => $val ) {
+            if ($key !== 'r') {
+                $params [$key] = $val;
+            }
+        }
+
+        return ( object ) $params;
+    }
+
+    /**
+     * Creates on object that represents an HTTP error.
+     *
+     * @param $code the
+     *            HTTP code (404, 401, etc...)
+     * @param $message The
+     *            message to be outputed
+     *
+     * @return on object that represents an HTTP error.
+     */
+    public function createHttpError($code, $message) {
+        return ( object ) array (
+                'httpError' => ( object ) array (
+                        'code' => $code,
+                        'message' => $message
+                )
+        );
+    }
+
+    /**
+     * Outputs the AJAX response.
+     * If it is an HTTP error, it will set
+     * HTTP code acordingly.
+     *
+     * @param $result The
+     *            AJAX request result as an object (or null).
+     *
+     */
+    private function echoResult($result) {
+        if ($result == null) {
+            // this may happen when the request results in
+            // in a binary output. In such cases, the output
+            // is performed directly by the LiteRequest object
+            return;
+        } else if (isset ( $result->httpError )) {
+            $this->echoHttpError ( $result->httpError );
+        } else {
+            header ( 'Content-Type: application/json' );
+            echo json_encode ( $result );
+        }
+    }
+
+    /**
+     * Outputs an HTTP error, setting the HTTP code acordingly.
+     *
+     * @param $httpError The
+     *            object that represents the httpError.
+     *
+     */
+    private function echoHttpError($httpError) {
+        $description = array (
+                400 => 'Bad Request',
+                401 => 'Unauthorized',
+                500 => 'Internal Server Error'
+        );
+        header ( 'Content-type:text/html; charset=UTF-8' );
+        header ( sprintf ( 'HTTP/1.1 %d %s', $httpError->code, $description [$httpError->code] ) );
+        die ( $httpError->message ); // note: a standard HTML object will be sent, see Firebug output
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/LiteRequestProcessor.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/LiteRequestProcessor.php
new file mode 100644 (file)
index 0000000..5a66032
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Expresso Lite
+ * LiteRequestProcessor acts as a hub to all Lite backend functions.
+ * When LiteRequestProcessor receives a request, it executes the
+ * following steps:
+ *   1. Creates it creates a 'request handler' object of the
+ *      appropriate class (a subclass of LiteRequest)
+ *   2. Initializes it with the currently available TineSession and
+ *      parameters
+ *   3. Tells the handler to process the request.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend;
+
+use \ReflectionClass;
+use \ReflectionException;
+use ExpressoLite\Exception\LiteException;
+
+class LiteRequestProcessor {
+
+    /**
+     * This is the default name space in which request handlers will be
+     * searched for
+     */
+    const LITE_REQUEST_NAMESPACE = 'ExpressoLite\\Backend\\Request\\';
+
+    /**
+     * Executes a request identified by a name, applying the informed parameters.
+     *
+     * @param $requestName The
+     *            request name, as defined by Lite.
+     * @param $params The
+     *            params to be associated to the request.
+     *
+     */
+    public function executeRequest($requestName, $params = array()) {
+        $requestHandler = $this->prepareLiteRequestHandler($requestName, $params );
+        $requestHandler->checkConstraints();
+
+        $result = $requestHandler->execute();
+
+        if ($result != null && is_string($result)) {
+            return json_decode ($result);
+        } else {
+            return $result;
+        }
+    }
+
+    /**
+     * Instantiates the appropriate request handler (an object of a subclass of LiteRequest)
+     * and initializes it with the current Tine Session and the informed params.
+     *
+     *
+     * @param $requestName The
+     *            request name, as defined by Lite. It must be the same name
+     *            of the class that is responsible to execute it.
+     * @param $params The
+     *            params to be associated to the request.
+     *
+     */
+    public function prepareLiteRequestHandler($requestName, $params = array()) {
+        $handlerClassName = self::LITE_REQUEST_NAMESPACE . ucfirst ( $requestName ); // uppercase first letter
+
+        $tineSession = TineSessionRepository::getTineSession ();
+        try {
+            $handlerClass = @new ReflectionClass ( $handlerClassName );
+        } catch ( ReflectionException $re ) {
+            throw new LiteException ( 'Invalid Lite Request: ' . $requestName, 0, 400 );
+        }
+
+        $functionHandler = $handlerClass->newInstance ();
+        $functionHandler->init ( $this, $tineSession, $params );
+
+        TineSessionRepository::storeTineSession ( $tineSession );
+        // store tineSession back in case any of its attributes changed.
+
+        // TODO: this is probably not necessary for most calls, as session attributes
+        // usually only change during login.
+
+        return $functionHandler;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/ChangeExpiredPassword.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/ChangeExpiredPassword.php
new file mode 100644 (file)
index 0000000..04046e0
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for changeExpiredPassword calls (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Exception\LiteException;
+
+class ChangeExpiredPassword extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $userName = $this->param('userName');
+        $oldPassword = $this->param('oldPassword');
+        $newPassword = $this->param('newPassword');
+
+        $response = $this->jsonRpc('Tinebase.changeExpiredPassword', (object) array(
+            'userName' => $userName,
+            'oldPassword' => $oldPassword,
+            'newPassword' => $newPassword
+        ));
+
+        if ($response->result->success === false) {
+            throw new LiteException($response->result->errorMessage);
+        }
+        return $jreq->result;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/CheckSessionStatus.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/CheckSessionStatus.php
new file mode 100644 (file)
index 0000000..f1265e7
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for EchoParams calls.
+ * It checks if the current tine session is logged in
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class CheckSessionStatus extends LiteRequest
+{
+
+    /**
+     * @var STATUS_INACTIVE Constant that indicates that
+     *      there is NOT a currently estabilished session
+     */
+    const STATUS_INACTIVE = 'inactive';
+
+    /**
+     * @var STATUS_ACTIVE Constant that indicates that there is
+     *     a currently estabilished session
+     */
+    const STATUS_ACTIVE = 'active';
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        if (!$this->tineSession->isLoggedIn() || 
+            !$this->tineSession->tineIsAuthenticated()) {
+            // User logged off explicitly OR
+            // Lite think its logged in, but Tine has no authentication info
+            $status = self::STATUS_INACTIVE; 
+        } else {
+            //Lite and Tine agree they are both logged in 
+            $status = self::STATUS_ACTIVE;
+        }
+
+        return (object) array(
+            'status' => $status
+        );
+    }
+
+    /**
+     * Allows this request to be executed even without a previously
+     * estabilished TineSession.
+     *
+     * @return true.
+     */
+    public function allowAccessWithoutSession()
+    {
+        return true;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/DeleteMessages.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/DeleteMessages.php
new file mode 100644 (file)
index 0000000..8044aff
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for deleteMessages calls  (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class DeleteMessages extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $msgIds = explode(',', $this->param('messages'));
+        $forever = $this->isParamSet('forever') && $this->param('forever') === '1';
+
+        if ($forever) {
+            return MessageUtils::addOrClearFlag($this->tineSession, $msgIds, "\\Deleted", true);
+        } else {
+            return MessageUtils::moveMessages($this->tineSession, $msgIds, '_trash_');
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/DownloadAttachment.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/DownloadAttachment.php
new file mode 100644 (file)
index 0000000..15cef91
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for downloadAttachment calls. This class has the special
+ * characteristic of generating output directly.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\TineTunnel\TineJsonRpc;
+use ExpressoLite\TineTunnel\Request;
+
+class DownloadAttachment extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        // This method will directly output the binary stream to client.
+        // Intended to be called on a "_blank" window.
+        $fileName = $this->param('fileName');
+        $messageId = $this->param('messageId');
+        $partId = $this->param('partId');
+
+        $mimeType = $this->isParamSet('forceDownload') ? null : $this->getMimeType($fileName);
+        if ($mimeType != null) {
+            //WITHOUT 'attachment' in header, will be opened in browser
+            header("Content-Disposition: filename=\"$fileName\"");
+            header('Content-Type: ' . $mimeType);
+        } else {
+            //WITH 'attachment' in header, will be downloaded
+            header("Content-Disposition: attachment; filename=\"$fileName\"");
+            header('Content-Type: application/octet-stream');
+            header('Content-Transfer-Encoding: binary');
+        }
+
+        $req = new Request();
+        $req->setUrl($this->tineSession->getTineUrl() . '?method=Tinebase.uploadTempFile');
+        $req->setCookieHandler($this->tineSession); //tineSession has the necessary cookies
+        $req->setBinaryOutput(true); // directly output binary stream to client
+        $req->setPostFields('requestType=HTTP&method=Expressomail.downloadAttachment&' .
+            "messageId=$messageId&partId=$partId&getAsJson=false");
+        $req->setHeaders(array(
+            'Connection: keep-alive',
+            'DNT: 1',
+            'User-Agent: ' . TineJsonRpc::DEFAULT_USERAGENT,
+            'Pragma: no-cache',
+            'Cache-Control: no-cache'
+        ));
+        $req->send(Request::POST);
+
+        // We set our Request with binaryOutput = true. Because of this,
+        // it has already outputed all the expected response. So, we return
+        // a null here.
+        return null;
+    }
+
+    /**
+     * Finds the file extension.
+     *
+     * @param $fileName
+     * @return The file extension.
+     */
+    private function getExtension($fileName)
+    {
+        $dotPos = strrpos($fileName, '.');
+        return ($dotPos === false) ? '' : substr($fileName, $dotPos);
+    }
+
+    /**
+     * Returns the mimetype associated with a file based on file extension.
+     *
+     * @param $fileName
+     * @return The associated mimetype.
+     */
+    private function getMimeType($fileName)
+    {
+        $mimeTypes = array(
+            '.txt' => 'text/plain',
+            '.pdf' => 'application/pdf',
+            '.png' => 'image/png',
+            '.jpe' => 'image/jpeg',
+            '.jpeg' => 'image/jpeg',
+            '.jpg' => 'image/jpeg',
+            '.gif' => 'image/gif',
+            '.bmp' => 'image/bmp',
+            '.ico' => 'image/vnd.microsoft.icon',
+            '.tiff' => 'image/tiff',
+            '.tif' => 'image/tiff',
+            '.svg' => 'image/svg+xml',
+            '.svgz' => 'image/svg+xml'
+        ); // these file extensions will be opened in browser, not downloaded
+
+
+        $ext = $this->getExtension($fileName);
+
+        if (isset($mimeTypes[$ext])) {
+            return $mimeTypes[$ext];
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/EchoParams.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/EchoParams.php
new file mode 100644 (file)
index 0000000..05a0d1e
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for EchoParams calls.
+ * This handler is created only for test purposes.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class EchoParams extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        return $this->params;
+    }
+
+    /**
+     * Allows this request to be executed even without a previously
+     * estabilished TineSession.
+     *
+     * @return true.
+     */
+    public function allowAccessWithoutSession()
+    {
+        return true;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetAllRegistryData.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetAllRegistryData.php
new file mode 100644 (file)
index 0000000..d2c4e22
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for GetAllRegistryData calls (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class GetAllRegistryData extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $response = $this->jsonRpc('Tinebase.getAllRegistryData');
+        $response->result->liteConfig = (object) array(
+            'classicUrl' => CLASSIC_URL,
+            'androidUrl' => ANDROID_URL,
+            'iosUrl' => IOS_URL,
+            'packageString' => PACKAGE_STRING
+        );
+        return $response->result;
+    }
+
+    /**
+     * Allows this request to be executed even without a previously
+     * estabilished TineSession.
+     *
+     * @return true.
+     */
+    public function allowAccessWithoutSession()
+    {
+        return true;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContact.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContact.php
new file mode 100644 (file)
index 0000000..a0acdf8
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for GetContactsByFilter calls.
+ *
+ * @package Backend
+ * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+class GetContact extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $response = $this->jsonRpc('Addressbook.getContact', (object) array(
+            'id' => $this->param('id')
+        ));
+
+        $tineContact = $response->result;
+
+        if ($tineContact->jpegphoto != 'images/empty_photo_blank.png') {
+            $mugshot = MessageUtils::getContactPicture(
+                $this->tineSession,
+                $tineContact->id,
+                strtotime($tineContact->creation_time),
+                90, 113);
+        } else {
+            $mugshot = '';
+        }
+
+        return (object) array(
+            'id' => $tineContact->id,
+            'name' => $tineContact->n_fn,
+            'email' => $tineContact->email,
+            'phone' => $tineContact->tel_work,
+            'mobile' => $tineContact->tel_cell,
+            'mugshot'=> $mugshot,
+            'otherFields' => $tineContact
+        );
+    }
+
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContactCatalogsCategories.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContactCatalogsCategories.php
new file mode 100644 (file)
index 0000000..18113f8
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for getContactsByFilter calls.
+ *
+ * @package Backend
+ * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class GetContactCatalogsCategories extends LiteRequest
+{
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        return (object) array(
+            'personal' => (object) array(
+                'title' => 'Catálogo Pessoal',
+                'containerPath' => '/personal/' . $this->tineSession->getAttribute('Tinebase.accountId'),
+                'pageLimit' => 9999, //no limit
+                'autoload' => true
+            ),
+            'corporate' => (object) array(
+                'title' => 'Catálogo Corporativo',
+                'containerPath' => '/shared',
+                'pageLimit' => 50,
+                'autoload' => false
+            ),
+        );
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContactsByFilter.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetContactsByFilter.php
new file mode 100644 (file)
index 0000000..fc9ef63
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for getContactsByFilter calls.
+ *
+ * @package Backend
+ * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class GetContactsByFilter extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $response = $this->jsonRpc('Addressbook.searchContacts', (object) array(
+            'filter' => $this->createFilter(),
+            'paging' => $this->createPaging()
+        ));
+        $contacts = $this->convertContactsFromTineToLite($response->result->results);
+
+        $result = (object) array(
+            'contacts' => $contacts,
+            'totalCount' => $response->result->totalcount
+        );
+        return $result;
+    }
+
+    /**
+     * Creates a filter object to be sent to Addressbook.searchContacts
+     *
+     * @return The filter object
+     */
+    private function createFilter()
+    {
+        $filters = array();
+        if ($this->isParamSet('query')) {
+            $filters[] = (object) array(
+                'field' => 'query',
+                'operator' => 'contains',
+                'value' => $this->param('query')
+            );
+        }
+        if ($this->isParamSet('containerPath')) {
+            $filters[] = (object) array(
+                'field' => 'container_id',
+                'operator' => 'in',
+                'value' => array(
+                    (object) array(
+                        'path' => $this->param('containerPath')
+                    )
+                )
+            );
+        }
+        return array(
+            (object) array(
+                'condition' => 'AND',
+                'filters' => $filters
+            )
+        );
+    }
+
+    /**
+     * Creates a paging object to be sent to Addressbook.searchContacts
+     *
+     * @return The paging object
+     */
+    private function createPaging()
+    {
+        return (object) array(
+            'sort' => array(
+                'n_fn',
+                'id'
+            ),
+            'dir' => 'ASC',
+            'start' => $this->param('start'),
+            'limit' => $this->param('limit')
+        );
+    }
+
+    /**
+     * Compares two values. If the $v1 > $v2, returns 1. If the $v1 < $v2, returns -1.
+     * If the two values are equal, returns 0. This is used by function compareContacts
+     * @see ExpressoLite\Backend\Request\LiteRequest::compareContacts
+     *
+     * @param $v1 The first value to be compared
+     * @param $v2 The second value to be compared
+     *
+     * @return int A value indicating which param is the greatest
+     */
+    private static function cmp($v1, $v2) {
+        if ($v1 == $v2) {
+            return 0;
+        } else {
+            return $v1 < $v2 ? -1 : 1;
+        }
+    }
+
+
+    /**
+     * Compares two contacts by name and then by id. Will return an int value indicating
+     * which contact should be first. This will be used for contact sorting.
+     * @see ExpressoLite\Backend\Request\LiteRequest::cmp
+     *
+     * @param $c1 The first contact to be compared
+     * @param $c2 The second contact to be compared
+     *
+     * @return int A value indicating which contact should be placed first
+     */
+    private static function compareContacts($c1, $c2) {
+
+        $cmpRes = self::cmp(strtolower($c1->name), strtolower($c2->name));
+        if ($cmpRes == 0) {
+            return self::cmp($c1->id, $c2->id);
+        } else {
+            return $cmpRes;
+        }
+    }
+
+    /**
+     * Converts an array of contacts in the format returned by Tine to
+     * an array of contacts in the format expected by Lite.
+     *
+     * @param $tineContacts An array of contacts as returned by Tine
+     *
+     * @return array An array of contacts in the format expected by Lite.
+     */
+    public function convertContactsFromTineToLite($tineContacts)
+    {
+        $liteContacts = array();
+        $first = true;
+        foreach ($tineContacts as $tineContact) {
+            $liteContact = (object) array(
+                'id' => $tineContact->id,
+                'name' => $tineContact->n_fn,
+                'email' => $tineContact->email,
+                'phone' => $tineContact->tel_work,
+                'mobile' => $tineContact->tel_cell,
+            );
+            $liteContacts[] = $liteContact;
+        }
+
+        //tine contacts sorting is broken, we have to sort the results ourselves
+        usort($liteContacts, array('ExpressoLite\\Backend\\Request\\GetContactsByFilter', 'compareContacts'));
+
+        return $liteContacts;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetMessage.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetMessage.php
new file mode 100644 (file)
index 0000000..1c271b7
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for getMessage calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class GetMessage extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $response = $this->jsonRpc('Expressomail.getMessage', (object) array(
+            'id' => $this->param('id')
+        ));
+
+        if (isset($response->result->headers->to) && $response->result->headers->to !== null) {
+            $mailsTo = explode(',', $response->result->headers->to);
+        } else {
+            $mailsTo = array();
+        }
+
+        for ($i = 0, $countMailsTo = count($mailsTo); $i < $countMailsTo; ++ $i) {
+            $mailsTo[$i] = htmlspecialchars(trim($mailsTo[$i]));
+        }
+
+        return (object) array(
+            'id' => $response->result->id,
+            'received' => $response->result->received,
+            'subject' => $response->result->subject,
+            'from_name' => $response->result->from_name,
+            'from_email' => $response->result->from_email,
+            'to' => $response->result->to,
+            'cc' => $response->result->cc,
+            'bcc' => $response->result->bcc,
+            'body' => $this->breakQuotedMessage($response->result->body),
+            'importance' => $response->result->importance,
+            'has_attachment' => $response->result->has_attachment,
+            'attachments' => $response->result->attachments,
+            'folder_id' => $response->result->folder_id
+        );
+    }
+
+    /**
+     * Returns an array of regular expressions that identify quoted messages.
+     *
+     */
+    private function getQuotedMessagePatterns() {
+        return array(
+                '/--+\s?((Mensagem encaminhada)|(Mensagem original)|(Original message)|(Originalnachricht))\s?--+/',
+                '/(<((div)|(font))(.{1,50})>)?Em \d+(\/|\.|-)\d+(\/|\.|-)\d+,? (a|à)(s|\(s\)) \d+:\d+( horas)?, (.{1,256})escreveu:/',
+                '/(<((div)|(font))(.{1,50})>)?Em \d+(\/|\.|-)\d+(\/|\.|-)\d+ \d+:\d+(:\d+)?, (.{1,256})escreveu:/',
+                '/(<((div)|(font))(.{1,50})>)?Em \d+ de (.{1,9}) de \d+ \d+:\d+(:\d+)?, (.{1,256})escreveu:/',
+                '/((On)|(Am)) \d{1,2}(\/|\.|-)\d{1,2}(\/|\.|-)\d{4} \d\d:\d\d(:\d\d)?, (.{1,256})((wrote)|(schrieb)):?/'
+        );
+    }
+
+    /**
+     * Separates the last message content from previously quoted messages
+     *
+     * @param The complete, unseparated message content
+     *
+     * @return object An object with the current message content (->message) and
+     *                quoted messages (->quoted).
+     */
+    private function breakQuotedMessage($message)
+    {
+        $idx = array();
+        foreach ($this->getQuotedMessagePatterns() as $pattern) {
+            if (preg_match($pattern, $message, $matches, PREG_OFFSET_CAPTURE))
+                $idx[] = $matches[0][1]; // append index of 1st occurrence of regex match
+        }
+        if (!empty($idx)) {
+            $idx = min($idx); // isolate index of earliest occurrence
+            $topMsg = $this->fixInlineAttachmentImages($this->trimLinebreaks(substr($message, 0, $idx)));
+            $quotedMsg = $this->fixInlineAttachmentImages($this->trimLinebreaks(substr($message, $idx)));
+            return (object) array(
+                'message' => $topMsg,
+                'quoted' => ($quotedMsg != '') ? $this->fixBrokenDiv($quotedMsg) : null
+            );
+        } else {
+            return (object) array(
+                'message' => $this->fixInlineAttachmentImages($this->trimLinebreaks($message)),
+                'quoted' => null
+            ); // no quotes found
+        }
+    }
+
+    /**
+     * Replaces inline attachment links in a message body so they may work within Lite.
+     *
+     * @param string $text the original message body from Tine
+     *
+     * @return the message body with replaced links
+     */
+    private function fixInlineAttachmentImages($text)
+    {
+        return preg_replace(
+            '/src="index\.php\?method=Expressomail\.downloadAttachment/',
+            'src="' . $this->param('ajaxUrl') . '?r=downloadAttachment&fileName=inlineAttachment',
+            $text);
+    }
+
+    /**
+     * Replaces inline attachment links in a message body so they may work within Lite.
+     *
+     * @param string $text the original message body from Tine
+     *
+     * @return the message body with replaced links
+     */
+    private function trimLinebreaks($text)
+    {
+        $text = trim($text);
+        if ($text != '') {
+            $text = preg_replace('/^(<(BR|br)\s?\/?>)+/', '', $text); // ltrim
+            $text = preg_replace('/(<(BR|br)\s?\/?>)+$/', '', $text); // rtrim
+        }
+        return $text;
+    }
+
+    /**
+     * If a </div> is found before a <div>, this would break Lite screen layout exhibition.
+     * In these cases, these function removes the first </div> in order to
+     * avoid this.
+     *
+     * @param string $text the message body
+     *
+     * @return the message body without a broken div if that was the case
+     */
+    private function fixBrokenDiv($text)
+    {
+        $firstClose = stripos($text, '</div>'); // an enclosing div before an opening one
+        $firstOpen = stripos($text, '<div');
+
+        if (($firstClose !== false && $firstOpen !== false && $firstClose < $firstOpen) ||
+            ($firstClose !== false && $firstOpen === false)) {
+            return preg_replace('/<\/div>/i', '', $text, 1);
+        } else {
+            return $text;
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetPersonalContacts.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/GetPersonalContacts.php
new file mode 100644 (file)
index 0000000..4554956
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for getPersonalContacts calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class GetPersonalContacts extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $response = $this->jsonRpc('Addressbook.searchEmailAddresss', (object) array(
+            'filter' => array(
+                (object) array(
+                    'field' => 'query',
+                    'label' => 'Pesquisa rápida',
+                    'operators' => array(
+                        'contains'
+                    )
+                ),
+                (object) array(
+                    'field' => 'email_query',
+                    'operator' => 'contains',
+                    'value' => '@'
+                ),
+                (object) array(
+                    'field' => 'container_id',
+                    'operator' => 'equals',
+                    'value' => '/personal/'.$this->tineSession->getAttribute('Tinebase.accountId')
+                )
+            ),
+            'paging' => (object) array(
+                'dir' => 'ASC',
+                'sort' => 'email'
+            )
+        ));
+        $contacts = array();
+        foreach ($response->result->results as $cont) {
+            $contactsEmail = null;
+            if (isset($cont->emails)) { // this contact is actually a group with many emails
+                $contactsEmail = array();
+                $addrs = explode(',', $cont->emails);
+                foreach ($addrs as $addr) {
+                    if (strpos($addr, '<') !== false) {
+                        $contactsEmail[] = substr($addr, strpos($addr, '<') + 1, strrpos($addr, '>') - strpos($addr, '<') - 1);
+                    }
+                }
+            } else {
+                $contactsEmail = array(
+                    $cont->email
+                ); // ordinary contact, 1 address
+            }
+            $contacts[] = (object) array(
+                'name' => $cont->n_fn,
+                'emails' => $contactsEmail
+            );
+        }
+        return $contacts; // both collected and personal contacts, merged
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/JoinTempFiles.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/JoinTempFiles.php
new file mode 100644 (file)
index 0000000..ac072f1
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for joinTempFiles calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class JoinTempFiles extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $tempFilesData = json_decode($this->param('tempFiles'));
+
+        $response = $this->jsonRpc('Tinebase.joinTempFiles', (object) array(
+            'tempFilesData' => $tempFilesData
+        ));
+
+        return $response->result;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/LiteRequest.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/LiteRequest.php
new file mode 100644 (file)
index 0000000..49470eb
--- /dev/null
@@ -0,0 +1,231 @@
+<?php
+/**
+ * Expresso Lite
+ * Abstract superclass for all handlers of Lite requests.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Exception\NoTineSessionException;
+use ExpressoLite\Backend\AjaxProcessor;
+use ExpressoLite\Exception\LiteException;
+use ExpressoLite\TineTunnel\TineSession;
+use ExpressoLite\Backend\LiteRequestProcessor;
+use ExpressoLite\Backend\TineSessionRepository;
+use ExpressoLite\Exception\UserMismatchException;
+
+abstract class LiteRequest
+{
+
+    /**
+     * @var array Will contain all input params to be processed by the request handler
+     */
+    protected $params;
+
+    /**
+     * @var LiteRequestProcessor The LiteRequestProcessor that created this LiteRequest
+     */
+    protected $processor;
+
+    /**
+     * @var TineSession The current TineSession available at TineSessionRepository
+     */
+    protected $tineSession;
+
+    /**
+     * This is the main method of this class, which will execute all
+     * logic related to the request. Needless to say, all subclasses
+     * must necessarily implement it.
+     *
+     * @return The request response.
+     */
+    public abstract function execute();
+
+    /**
+     * Inits the request with useful information (see params for description)
+     *
+     * @param LiteRequestProcessor $processsor The processor that created this request
+     * @param TineSession $tineSession The current estabilished tineSession (if there is one)
+     * @param array $params The params that were informed to the request
+     *
+     */
+    public function init(LiteRequestProcessor $processsor, TineSession $tineSession = null, $params = array())
+    {
+        if (is_array($params)) {
+            $params = (object) $params;
+        }
+
+        $this->params = $params;
+        $this->processor = $processsor;
+        $this->tineSession = $tineSession;
+
+    }
+
+    /**
+     * Checks all constraints that can prevent this request from 
+     * being executed are met.
+     *
+     */
+    public function checkConstraints() 
+    {
+        $this->checkIfSessionIsLoggedIn();
+        $this->checkIfSessionUserIsValid();
+    }
+
+    /**
+     * This function throws an exception if user is not logged in when
+     * the request is executed, unless this request explicitly allows so
+     *
+     */
+    private function checkIfSessionIsLoggedIn()
+    {
+        if (!$this->allowAccessWithoutSession() && !$this->tineSession->isLoggedIn()) {
+            throw new NoTineSessionException('This request cannot be processed without a previously estabilished tine session');
+        }
+    }
+
+    /**
+     * Checks if user defined in the back-end is the same in the front-end, throwing
+     * exceptions and logging the errors.
+     *
+     */
+    protected function checkIfSessionUserIsValid()
+    {
+        $clientUser = isset($_COOKIE['user']) ? $_COOKIE['user'] : null;
+        $tineSessionUser = $this->tineSession->getAttribute('Expressomail.email');
+
+        if ($tineSessionUser != null && $clientUser != $tineSessionUser) {
+            $this->resetTineSession();
+            error_log('POSSIBLE SESSION HIJACKING! Client user: ' . $clientUser . '; TineSession user: '. $tineSessionUser);
+            throw new UserMismatchException();
+        }
+    }
+
+    /**
+     * This method indicates it the LiteRequest may be invoked even without
+     * a previously estabilished TineSession. By default, it returns false,
+     * so invoking the request with a LiteRequestProcessor will return a
+     * NoTineSessionException. However, this mehod should be overriden in
+     * situations in which calling a request without a session is allowed.
+     *
+     * @return true or false, indicating whether the request may be invoked
+     * without a TineSession
+     */
+    public function allowAccessWithoutSession()
+    {
+        // this method is supposed to be overriden by calls that
+        // may be executed even without a previously estabilished
+        // tine session (i.e. login)
+        return false;
+    }
+
+    /**
+     * Utility method to throw a liteException corresponding to
+     * some http code
+     *
+     * @param $code The HTTP code to be sent to the user (like 404, 401, etc...)
+     * @param $message The message that will be shown in the log
+     *
+     */
+    public function httpError($code, $message)
+    {
+        throw new LiteException($message, 0, $code);
+    }
+
+    /**
+     * Returns true if the user has already logged in, false otherwise.
+     *
+     */
+    public function isLoggedIn()
+    {
+        $this->tineSession !== null && $this->tineSession->isLoggedIn();
+    }
+
+    /**
+     * Returns the value of one of the request parameters.
+     *
+     * @param string $paramName The name of the parameter we want to get the value of
+     *
+     * @return string the param value
+     */
+    public function param($paramName)
+    {
+        return $this->params->{$paramName};
+    }
+
+    /**
+     * Returns true if the specified parameter was set for this request,
+     * false otherwise.
+     *
+     * @param string $paramName The name of the parameter we want to check
+     *
+     * @return boolean true if the parameter was informed to this request,
+     *                 false otherwise
+     */
+    public function isParamSet($paramName)
+    {
+        return isset($this->params->{$paramName});
+    }
+
+    /**
+     * Utility method to use the current estabilished TineSession to
+     * make a JSON RPC call to Tine. If Tine returns an error, this will lead to
+     * an exception
+     *
+     * @param string $method Tine's method name (i.e. Tinebase.getAllRegistryData)
+     * @param array $params The parameters to be sent to Tine
+     * @param boolean $acceptErrors If set to true, this will suppress exception throwing
+     *       when Tine returns an error
+     *
+     * @return object The response of the JSON RPC call made to Tine
+     *
+     */
+    public function jsonRpc($method, $params = array(), $acceptErrors = false)
+    {
+        return $this->tineSession->jsonRpc($method, $params, $acceptErrors);
+    }
+
+    /**
+     * Utility method to get an attribute associated with the current TineSession
+     *
+     * @param string $attrName The name of the attribute we want to get the value of
+     *
+     * @return The value of the session attribute
+     */
+    public function getSessionAttribute($attrName)
+    {
+        return $this->tineSession->getAttribute($attrName);
+    }
+
+    /**
+     * Utility method that does the following: it tries to get the attribute of an object:
+     * $object->{$field}. If this field is set, than the field value is returned. If the
+     * field is NOT set, it returns $defaultValue.
+     *
+     * @param $object The objetct that has the desired field
+     * @param string $field The name of the field we want to get the value of
+     * @param $defaultValue The value to be returned when the field is not set
+     *
+     * @return The object attribute or the default value.
+     */
+    public function coalesce($object, $field, $defaultValue)
+    {
+        return isset($object->{$field}) ? $object->{$field} : $defaultValue;
+    }
+
+    /**
+     * Dumps the current tineSession associated to this request
+     * with a new one provided by TineSessionRepository
+     *
+     * @return TineSession The new TineSession
+     *
+     */
+    public function resetTineSession() {
+        $this->tineSession = TineSessionRepository::resetTineSession();
+    }
+}
+
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Login.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Login.php
new file mode 100644 (file)
index 0000000..3c631b4
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for Login calls. This handler connects the current TineSession
+ * to the Tine server, allowing it to perform authenticated calls from
+ * then on.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Exception\CaptchaRequiredException;
+
+class Login extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        if (! $this->isParamSet('user') || ! $this->isParamSet('pwd')) {
+            $this->httpError(400, 'É necessário informar login e senha.');
+        }
+
+        try {
+            $this->resetTineSession();
+            $result = $this->tineSession->login(
+                $this->param('user'),
+                $this->param('pwd'),
+                $this->isParamSet('captcha') ? $this->param('captcha') : null
+            );
+        } catch (PasswordExpiredException $pe) {
+            return (object) array(
+                'success' => false,
+                'expired' => true
+            );
+        } catch (CaptchaRequiredException $cre) {
+            return (object) array(
+                    'success' => false,
+                    'captcha' => $cre->getCaptcha()
+            );
+        }
+
+        if ($result) {
+            $cookiePath = str_replace('accessible/', '', $_SERVER['REQUEST_URI']);
+            //we remove 'accessible/' suffix from current path.
+            //This way, the cookie will always be set to all modules,
+            //even if it was started by the accessible module
+
+            setrawcookie( // setrawcookie deals better with @ in the cookie value
+                'user', $this->param('user'),
+                time()+60*60*24*30, // expires = actual time + 30 days
+                $cookiePath
+            );
+
+            $_COOKIE['user'] = $this->param('user');
+            //setrawcookie() does not update the $_COOKIE array with the new cookie.
+            //So, we do this manually to avoid problems with checkIfSessionUserIsValid
+            //later on
+        }
+
+        $this->checkIfSessionUserIsValid();
+        // Its better to check if the tine user matches Expresso Lite user
+        // right away
+
+        return (object) array(
+            'success' => $result,
+            'userInfo' => (object) array (
+                'mailAddress' => $this->tineSession->getAttribute('Expressomail.email'),
+                'mailSignature' => $this->tineSession->getAttribute('Expressomail.signature'),
+                'mailBatch' => MAIL_BATCH,
+            )
+        );
+    }
+
+    /**
+     * Allow this request to be called even without a previously estabilished
+     * TineSession (after all, THIS is the request that estabilishes the TineSession!)
+     *
+     * @return true
+     */
+    public function allowAccessWithoutSession()
+    {
+        return true;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Logoff.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Logoff.php
new file mode 100644 (file)
index 0000000..9b7bcde
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for Logoff calls. This disconnects the current TineSession.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class Logoff extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        try {
+            $response = $this->tineSession->logout();
+        } catch (\Exception $e) {
+            error_log('Error during logoff: ' . $e->getMessage());
+            $response = array(
+                'status' => 'error',
+                'message' => $e->getMessage()
+            );
+        }
+
+        $this->resetTineSession();
+        session_destroy();
+        //resetting tine session and detroying the session may be
+        //redundant, but its an extra precaution to avoid reuse
+        //of invalidated sessions
+
+        return (object) $response;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MarkAsHighlighted.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MarkAsHighlighted.php
new file mode 100644 (file)
index 0000000..a39606f
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for markAsHighlighted calls  (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class MarkAsHighlighted extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $asHighlighted = $this->param('asHighlighted') === '1';
+        $msgIds = explode(',', $this->param('ids'));
+
+        return MessageUtils::addOrClearFlag($this->tineSession, $msgIds, "\\Flagged", $asHighlighted);
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MarkAsRead.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MarkAsRead.php
new file mode 100644 (file)
index 0000000..158a972
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for markAsRead calls  (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class MarkAsRead extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $msgIds = explode(',', $this->param('ids')); // comma-separated into array
+        $asRead = $this->param('asRead') === '1';
+
+        return MessageUtils::addOrClearFlag($this->tineSession, $msgIds, "\\Seen", $asRead);
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MoveMessages.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/MoveMessages.php
new file mode 100644 (file)
index 0000000..37e169e
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for moveMessages calls (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class MoveMessages extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $msgIds = explode(',', $this->param('messages'));
+        $folder = $this->param('folder');
+
+        return MessageUtils::moveMessages($this->tineSession, $msgIds, $folder);
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SaveMessage.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SaveMessage.php
new file mode 100644 (file)
index 0000000..60cfd29
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for saveMessage calls (self explanatory).
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class SaveMessage extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $subject = $this->param('subject');
+        $body = $this->param('body');
+        $to = $this->toArrayOfEmails($this->param('to'));
+        $cc = $this->toArrayOfEmails($this->param('cc'));
+        $bcc = $this->toArrayOfEmails($this->param('bcc'));
+        $isImportant = $this->param('isImportant') == '1';
+        $replyToId = $this->emptyAsNull($this->param('replyToId'));
+        $forwardFromId = $this->emptyAsNull($this->param('forwardFromId'));
+        $origDraftId = $this->emptyAsNull($this->param('origDraftId'));
+        $wantConfirm = $this->isParamSet('wantConfirm') ? $this->param('wantConfirm') == '1' : false;
+
+        $attachs = $this->param('attachs');
+        if ($attachs != '') {
+            $attachs = json_decode($attachs);
+        } else {
+            $attachs = array();
+        }
+
+        $response = $this->jsonRpc('Expressomail.saveMessage', (object) array(
+            'recordData' => MessageUtils::buildMessageForSaving(
+                $this->tineSession, $subject, $body, $to, $cc, $bcc,
+                $isImportant, $replyToId, $forwardFromId, $origDraftId, $attachs, $wantConfirm)
+        ));
+
+        return $response->result;
+    }
+
+    /**
+     * Converts a comma separated list of emails to an array
+     *
+     * @param string $emails Comma separated list of emails
+     * 
+     * @return array Array of email strings
+     */
+    private function toArrayOfEmails($emails)
+    {
+        if ($emails != '') {
+            return explode(',', $emails);
+        } else {
+            return array();
+        }
+    }
+
+    /**
+     * Utility method to convert empty strings '' to null, if necessary
+     *
+     * @param string $string
+     *
+     * @return null if the string was '', the original $string otherwise
+     */
+     private function emptyAsNull($string)
+    {
+        return $string == '' ? null : $string;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SaveMessageDraft.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SaveMessageDraft.php
new file mode 100644 (file)
index 0000000..f91c38a
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for saveMessageDraft calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014-2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class SaveMessageDraft extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $draftFolderId = $this->param('draftFolderId');
+        $subject = $this->param('subject');
+        $body = $this->param('body');
+        $to = $this->toArrayOfEmails($this->param('to'));
+        $cc = $this->toArrayOfEmails($this->param('cc'));
+        $bcc = $this->toArrayOfEmails($this->param('bcc'));
+        $isImportant = $this->param('isImportant') == '1';
+        $replyToId = $this->emptyAsNull($this->param('replyToId'));
+        $forwardFromId = $this->emptyAsNull($this->param('forwardFromId'));
+        $origDraftId = $this->emptyAsNull($this->param('origDraftId'));
+        $attachs = $this->param('attachs') != '' ? json_decode($this->param('attachs')) : array(); // array of tempFile objects
+
+        $recordData = MessageUtils::buildMessageForSaving(
+            $this->tineSession, $subject, $body, $to, $cc, $bcc,
+            $isImportant, $replyToId, $forwardFromId, $origDraftId, $attachs
+        );
+
+        $this->jsonRpc('Expressomail.saveMessageInFolder', (object) array(
+            'folderName' => 'INBOX/Drafts',
+            'recordData' => $recordData
+        ));
+
+        $draftMsg = $this->processor->executeRequest('searchHeadlines', array(
+            'folderIds' => $draftFolderId,
+            'start' => 0,
+            'limit' => 1
+        )); // newest draft
+
+
+        $result = MessageUtils::addOrClearFlag($this->tineSession, array(
+            $draftMsg[0]->id
+        ), "\\Seen", true);
+        // because Tine saves new draft as unread
+
+
+        return $result;
+    }
+
+    /**
+     * Converts a comma separated list of emails to an array
+     *
+     * @param string $emails Comma separated list of emails
+     *
+     * @return array Array of email strings
+     */
+    private function toArrayOfEmails($emails)
+    {
+        if ($emails != '') {
+            return explode(',', $emails);
+        } else {
+            return array();
+        }
+    }
+
+    /**
+     * Utility method to convert empty strings '' to null, if necessary
+     *
+     * @param string $string
+     *
+     * @return null if the string was '', the original $string otherwise
+     */
+    private function emptyAsNull($string)
+    {
+        return $string == '' ? null : $string;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchContactsByEmail.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchContactsByEmail.php
new file mode 100644 (file)
index 0000000..420c167
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for searchContactsByEmail calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014-2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\TineTunnel\Request;
+use ExpressoLite\TineTunnel\TineJsonRpc;
+
+class SearchContactsByEmail extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $emails = explode(',', $this->param('emails'));
+
+        $filters = array();
+        foreach ($emails as $email) {
+            $filters[] = (object) array(
+                'field' => 'email_query',
+                'operator' => 'contains',
+                'value' => $email
+            );
+        }
+        $response = $this->jsonRpc('Addressbook.searchContacts', (object) array(
+            'filter' => array(
+                (object) array(
+                    'condition' => 'OR',
+                    'filters' => array(
+                        (object) array(
+                            'condition' => 'OR',
+                            'filters' => $filters,
+                            'label' => 'Contacts'
+                        )
+                    )
+                )
+            ),
+            'paging' => (object) array(
+                'sort' => 'n_fn',
+                'dir' => 'ASC',
+                'start' => 0,
+                'limit' => 50 //count($emails)
+            )
+        ));
+        $contacts = array();
+        foreach ($response->result->results as $contact) {
+            $contacts[] = (object) array(
+                'id' => $contact->id,
+                'isDeleted' => $contact->is_deleted !== '0',
+                'created' => strtotime($contact->creation_time),
+                'email' => $contact->email,
+                'mugshot' => $this->getContactPicture($contact->id, strtotime($contact->creation_time), 90, 113),
+                //'mugshotUrl' => $this->assemblyMugshotUrl($contact->id, strtotime($contact->creation_time), 90, 113),
+                'name' => $contact->n_fn,
+                'phone' => $contact->tel_work,
+                'cpf' => sprintf('%011d', (int) $contact->account_id),
+                'org' => $contact->org_name,
+                'orgUnit' => $contact->org_unit
+            );
+        }
+        return $this->removeDuplicatedContacts($contacts);
+    }
+
+    /**
+     * Returns the binary content of a contact picture
+     *
+     * @param $contactId The id of the contact
+     * @param $creationTime The creation time of the contact
+     * @param $cx The desired picture width
+     * @param $cy The desired picture height
+     *
+     * @return The binary contact of the contact picture.
+     */
+    public function getContactPicture($contactId, $creationTime, $cx, $cy)
+    {
+        //we need to make a 'custom' request to get the contact picture, so
+        //we build a Request object manually instead of relying on tineSession
+        //to do it for us
+        $req = new Request();
+        $req->setBinaryOutput(false); // do not directly output the binary stream to client
+        $req->setCookieHandler($this->tineSession); //tineSession has the necessary cookies
+        $req->setUrl($this->assemblyMugshotUrl($contactId, $creationTime, $cx, $cy));
+        $req->setHeaders(array(
+            'Connection: keep-alive',
+            'DNT: 1',
+            'User-Agent: ' . TineJsonRpc::DEFAULT_USERAGENT,
+            'Pragma: no-cache',
+            'Cache-Control: no-cache'
+        ));
+        $mugshot = $req->send();
+
+        return ($mugshot === null || substr($mugshot, 0, 14) === '<!DOCTYPE html') ?
+            '' : $mugshot; // dummy if binaryOutput(true)
+    }
+
+    /**
+     * Assemblies the URL to retrieve the mugshot of a given contact.
+     *
+     * @param $contactId    The ID of the contact.
+     * @param $creationTime Timestamp of contact creation date.
+     * @param $cx           Desired picture width, in pixels.
+     * @param $cy           Desired picture height, in pixels.
+     *
+     * @return Mugshot URL for the contact.
+     */
+    private function assemblyMugshotUrl($contactId, $creationTime, $cx, $cy)
+    {
+        return $this->tineSession->getTineUrl() .
+            '?method=Tinebase.getImage' .
+            '&application=Addressbook' .
+            '&location=' .
+            '&id='.$contactId .
+            '&width='.$cx .
+            '&height='.$cy .
+            '&ratiomode=0' .
+            '&mtime='.$creationTime.'000';
+    }
+
+    /**
+     * This method ensures that there is only one single contact for each e-mail,
+     * removing duplicate contacts. Note that in these cases, this method will always
+     * keep the contact with a mugshot over one without it.
+     *
+     * @param array $contacts The array of contacts to be trimmed of duplicates
+     *
+     * @return The $array without duplicate contacts
+     */
+    private function removeDuplicatedContacts(array $contacts)
+    {
+        $ret = array();
+        foreach ($contacts as $contact) {
+            $willAdd = true;
+            for ($i = 0, $tot = count($ret); $i < $tot; ++ $i) {
+                if ($contact->email === $ret[$i]->email) { // duplicated contact
+                    if ($ret[$i]->mugshot == '' || $ret[$i]->mugshot === null) { // our current contact has no mugshot
+                        if ($contact->mugshot != '' && $contact->mugshot !== null)
+                            $ret[$i] = $contact; // replace
+                    }
+                    $willAdd = false;
+                    break;
+                }
+            }
+            if ($willAdd)
+                $ret[] = $contact;
+        }
+        return $ret;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchContactsByToken.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchContactsByToken.php
new file mode 100644 (file)
index 0000000..f317b6b
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for searchContactsByToken calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014-2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class SearchContactsByToken extends LiteRequest
+{
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $token = $this->param('token');
+        $start = $this->isParamSet('start') ? $this->param('start') : 0; // pagination
+        $limit = $this->isParamSet('limit') ? $this->param('limit') : 50;
+
+        $response = $this->jsonRpc('Addressbook.searchContacts', (object) array(
+            'filter' => array(
+                (object) array( // search on all catalogs
+                    'field' => 'query',
+                    'id' => 'quickFilter',
+                    'operator' => 'contains',
+                    'value' => $token
+                )
+            ),
+            'paging' => (object) array(
+                'dir' => 'ASC',
+                'limit' => $limit,
+                'sort' => 'n_fileas',
+                'start' => $start
+            )
+        ));
+        $contacts = array();
+        foreach ($response->result->results as $contact) {
+            $contacts[] = (object) array(
+                'cpf' => $contact->id, // yes, returned object is inconsistent, see searchContactsByEmail()
+                'email' => $contact->email,
+                'name' => $contact->n_fn,
+                'isDeleted' => $contact->is_deleted !== '',
+                'org' => $contact->org_unit
+            );
+        }
+        return (object) array(
+            'contacts' => $contacts,
+            'totalCount' => $response->result->totalcount // useful for pagination
+        );
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchEvents.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchEvents.php
new file mode 100644 (file)
index 0000000..01afaf5
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for searchEvent calls.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Fabiano Kuss <fabiano.kuss@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace ExpressoLite\Backend\Request;
+use \DateTime;
+use \DateTimeZone;
+
+class SearchEvents extends LiteRequest
+{
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $start = $this->isParamSet('start') ? $this->param('start') : 0; // pagination
+        $limit = $this->isParamSet('limit') ? $this->param('limit') : 50;
+
+        $from = $this->param('from');
+        $until = $this->param('until');
+
+        $response = $this->jsonRpc('Calendar.searchEvents', (object) array(
+            'filter' => array(
+                (object) array( // search on all catalogs
+                    'field' => 'period',
+                    'operator' => 'within',
+                    'value' => array(
+                        'from' => $from,
+                        'until' => $until
+                    )
+                )
+            ),
+            'paging' => (object) array(
+                'dir' => 'ASC',
+                'limit' => $limit,
+                'start' => $start
+            )
+        ));
+        return (object) array(
+            'totalCount' => $response->result->totalcount,
+            'events' => $this->formatEvents($response->result->results)
+        );
+    }
+
+    /**
+     * Formats an array of Tine raw events stripping away the garbage.
+     *
+     * @param array $events Raw Tine events.
+     *
+     * @return array Array of formatted events.
+     */
+    private function formatEvents(array $events)
+    {
+        $ret = array();
+        foreach ($events as $e) {
+            $ret[] = (object) array(
+                'id' => $e->id,
+                'from' => $this->parseTimeZone($e->dtstart, $e->originator_tz),
+                'until' => $this->parseTimeZone($e->dtend, $e->originator_tz),
+                'summary' => $e->summary,
+                'description' => $e->description,
+                'location' => $e->location,
+                'color' => $this->getEventColor($e),
+                'confirmation' => $this->getUserConfirmation($e),
+                'organizer' => $this->getUserInformation($e->organizer),
+                'attendees' => $this->getAttendees($e)
+            );
+        }
+        return $ret;
+    }
+
+    /**
+     * Given a datetime string, formats it according to the given timezone.
+     *
+     * @param string $strTime String like '2015-08-05 13:50:00'.
+     * @param string $strZone PHP DateTimeZone string, like 'America_SaoPaulo'.
+     *
+     * @return string String with datetime, like '2015-08-05 13:50:00', UTC±0 timezone.
+     */
+    private function parseTimeZone($strTime, $strZone)
+    {
+        $d = new DateTime($strTime, new DateTimeZone($strZone));
+        $d->setTimezone(new DateTimeZone('UTC'));
+        return $d->format('Y-m-d H:i:s');
+    }
+
+    /**
+     * Get the hexadecimal color code for the given event.
+     *
+     * @param stdClass $event The event object to retrieve the color.
+     *
+     * @return string Hexadecimal color code.
+     */
+    private function getEventColor($event)
+    {
+        // For some odd reason, "container_id" may be a full object with all
+        // information, or just a number without anything useful.
+        return is_object($event->container_id) ?
+            $event->container_id->color :
+            $this->tineSession->getAttribute('Calendar.defaultEventColor');
+    }
+
+    /**
+     * Tells confirmation status of current user upon the event.
+     *
+     * @param stdClass $event The event object to retrieve the confirmation status.
+     *
+     * @return string Current user confirmation status upon the event.
+     */
+    private function getUserConfirmation($event)
+    {
+        foreach ($event->attendee as $atd) {
+            if (is_object($atd->user_id)) {
+                if ($this->tineSession->getAttribute('Expressomail.email') === $atd->user_id->email) {
+                    return $atd->status;
+                }
+            }
+        }
+        return ''; // if user removed himself from the event, should never happen
+    }
+
+    /**
+     * Returns all event attendees.
+     *
+     * @param stdClass $event The event object to retrieve the attendees.
+     *
+     * @return array[stdClass] All event attendees.
+     */
+    private function getAttendees($event)
+    {
+        $ret = array();
+        foreach ($event->attendee as $atd) {
+            if (is_object($atd->user_id)) {
+                $objAtd = $this->getUserInformation($atd->user_id);
+                $objAtd->confirmation = $atd->status;
+                $ret[] = $objAtd;
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * Filters information about an user.
+     *
+     * @param stdClass $user Raw Tine user object from event.
+     *
+     * @return stdClass Filtered event user information.
+     */
+    private function getUserInformation($user)
+    {
+        return (object) array(
+            'id' => $user->id,
+            'name' => $user->n_fn,
+            'email' => $user->email,
+            'region' => $user->adr_one_region,
+            'orgUnit' => $user->org_unit,
+            'phone' => $user->tel_work
+        );
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchFolders.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchFolders.php
new file mode 100644 (file)
index 0000000..7c836b4
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for searchFolders calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class SearchFolders extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $recursive = $this->isParamSet('recursive') ? $this->param('recursive') == 1 : false;
+        $parentFolder = $this->isParamSet('parentFolder') ? $this->param('parentFolder') : '';
+        $accountId = $this->getSessionAttribute('Expressomail.accountId');
+
+        return $this->searchFolders($parentFolder, $recursive, $accountId);
+    }
+
+    /**
+     * Searches all folders belonging to a parent folder
+     *
+     * @param $parentFolder The parent folder id
+     * @param $recursive Indicates if subfolders should be searched recursively and returned as well
+     * @param $accountId The account id of the folder's owner
+     *
+     * @return An array containing the folders.
+     */
+    public function searchFolders($parentFolder, $recursive, $accountId)
+    {
+        $response = $this->jsonRpc('Expressomail.searchFolders', (object) array(
+            'filter' => array(
+                (object) array(
+                    'field' => 'account_id',
+                    'operator' => 'equals',
+                    'value' => $accountId
+                ),
+                (object) array(
+                    'field' => 'globalname',
+                    'operator' => 'equals',
+                    'value' => $parentFolder
+                )
+            )
+        ));
+
+        $enToPtTrans = array(
+            'INBOX' => 'Caixa de entrada',
+            'Drafts' => 'Rascunhos',
+            'Sent' => 'Enviados',
+            'Templates' => 'Modelos',
+            'Trash' => 'Lixeira'
+        );
+
+        $fldrs = array();
+
+        foreach ($response->result->results as $result) {
+            if (array_key_exists($result->localname, $enToPtTrans)) {
+                $result->localname = $enToPtTrans[$result->localname];
+            }
+
+            $fldrs[] = (object) array(
+                'id' => $result->id,
+                'globalName' => $result->globalname,
+                'localName' => $result->localname,
+                'hasSubfolders' => $result->has_children,
+                'subfolders' => $recursive && $result->has_children ? $this->searchFolders($result->globalname, true, $accountId) : array(),
+                'totalMails' => (int) $this->coalesce($result, 'cache_totalcount', 0),
+                'unreadMails' => (int) $this->coalesce($result, 'cache_unreadcount', 0),
+                'recentMails' => (int) $this->coalesce($result, 'cache_recentcount', 0),
+                'quotaLimit' => (int) $this->coalesce($result, 'quota_limit', 0),
+                'quotaUsage' => (int) $this->coalesce($result, 'quota_usage', 0),
+                'systemFolder' => $result->system_folder,
+                'messages' => array(),
+                'threads' => array()
+            );
+        }
+        return $fldrs;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchHeadlines.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SearchHeadlines.php
new file mode 100644 (file)
index 0000000..2ca48e5
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for searchHeadlines calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014-2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class SearchHeadlines extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $folderIds = explode(',', $this->param('folderIds'));
+        $start = (int) $this->param('start');
+        $limit = (int) $this->param('limit');
+        $searchTerm = $this->isParamSet('what') ? $this->param('what') : '';
+
+        $accountId = $this->getSessionAttribute('Expressomail.accountId');
+        foreach ($folderIds as &$fid) {
+            $fid = "/$accountId/$fid";
+        }
+
+        $tineMessages = $this->getTineMessages($searchTerm, $folderIds, $start, $limit);
+        $liteHeadlines = $this->convertTineMessagesToLiteHeadlines($tineMessages);
+        return ($searchTerm === '') ?
+            $liteHeadlines->headlines : $this->createVirtualSearchFolder($searchTerm, $liteHeadlines);
+    }
+
+    /**
+     * Connects to tine to return an array of messages that match a specified criteria.
+     *
+     * @param string $what      A text that is part of the message
+     * @param array  $folderIds The ids of the folders in which the message should be searched
+     * @param int    $start     The index of the first result to be returned (used for paging purposes)
+     * @param int    $limit     The max number of results to be returned (used for paging purposes)
+     *
+     * @return Array of messages as returned by Tine
+     */
+    private function getTineMessages($what, array $folderIds, $start, $limit)
+    {
+        $response = $this->jsonRpc('Expressomail.searchMessages', (object) array(
+            'filter' => array(
+                (object) array(
+                    'condition' => 'AND',
+                    'filters' => array(
+                        (object) array(
+                            'field'    => 'query',
+                            'operator' => 'contains',
+                            'value'    => $what
+                        ),
+                        (object) array(
+                            'field'    => 'path',
+                            'operator' => 'in',
+                            'value'    => $folderIds
+                        )
+                    )
+                )
+            ),
+            'paging' => (object) array(
+                'sort'  => 'received',
+                'dir'   => 'DESC',
+                'start' => $start,
+                'limit' => $limit
+            )
+        ));
+        return (object) array(
+            'headlines'  => $response->result->results,
+            'totalCount' => $response->result->totalcount // for paging, if needed
+        );
+    }
+
+    /**
+     * Search results are returned within a virtual folder, this method creates this folder.
+     *
+     * @param string   $searchTerm   Text being searched
+     * @param stdClass $searchResult Result of the search, with headlines array
+     *
+     * @return Virtual folder object
+     */
+    private function createVirtualSearchFolder($searchTerm, $searchResult)
+    {
+        return (object)array( // return an artificial folder
+            'id'            => null, // ID and globalName are set to null
+            'globalName'    => null,
+            'localName'     => $what,
+            'hasSubfolders' => false,
+            'subfolders'    => array(),
+            'totalMails'    => count($searchResult->headlines) ? $searchResult->totalCount : 0,
+            'unreadMails'   => 0,
+            'recentMails'   => 0,
+            'quotaLimit'    => 0,
+            'quotaUsage'    => 0,
+            'systemFolder'  => false,
+            'messages'      => $searchResult->headlines,
+            'threads'       => array()  // not populated here
+        );
+    }
+
+    /**
+     * Converts an array of messages (as returned by Tine) to the format expected by Lite.
+     *
+     * @param array $tineMessages Then array of messages as returned by Tine
+     *
+     * @return Object with array of messages as expected by Lite
+     */
+    private function convertTineMessagesToLiteHeadlines($tineMessages)
+    {
+        $headlines = array();
+        foreach ($tineMessages->headlines as $mail) {
+            $headlines[] = (object) array(
+                'id'      => $mail->id,
+                'subject' => ($mail->subject !== null) ? $mail->subject : '',
+                'to'      => ($mail->to !== null) ? $mail->to : array(),
+                'cc'      => ($mail->cc !== null) ? $mail->cc : array(),
+                'bcc'     => ($mail->bcc !== null) ? $mail->bcc : array(), // brings only 1 email
+                'from'    => (object) array(
+                    'name'  => $mail->from_name,
+                    'email' => $mail->from_email
+                ),
+                'unread'        => ! in_array("\\Seen", $mail->flags),
+                'draft'         => in_array("\\Draft", $mail->flags),
+                'flagged'       => in_array("\\Flagged", $mail->flags),
+                'replied'       => in_array("\\Answered", $mail->flags),
+                'forwarded'     => in_array("Passed", $mail->flags),
+                'important'     => $mail->importance,
+                'signed'        => $mail->structure->contentType === 'multipart/signed',
+                'wantConfirm'   => $mail->reading_conf,
+                'received'      => strtotime($mail->received), // timestamp
+                'size'          => (int) $mail->size, // bytes
+                'hasAttachment' => $mail->has_attachment,
+                'attachments'   => null, // not populated here
+                'body'          => null
+            );
+        }
+        return (object) array(
+            'headlines'  => $headlines,
+            'totalCount' => $tineMessages->totalCount // for paging, if needed
+        );
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SetEventConfirmation.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/SetEventConfirmation.php
new file mode 100644 (file)
index 0000000..79cf380
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for searchEvent calls.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace ExpressoLite\Backend\Request;
+
+class SetEventConfirmation extends LiteRequest
+{
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $eventId = $this->param('id');
+        $confirmation = $this->param('confirmation');
+
+        $rawEvent = $this->getRawEventById($eventId);
+        $this->setRawEventConfirmation($rawEvent, $confirmation);
+        $this->jsonRpc('Calendar.saveEvent', (object) array(
+            'recordData' => $rawEvent,
+            'checkBusyConflicts' => 1 // faster; can't have a conflict when confirming an event
+        ));
+
+        return (object) array(
+            'eventId' => $eventId, // no need to return the whole event object again, just confirm
+            'confirmation' => $confirmation
+        );
+    }
+
+    /**
+     * Retrieves a raw Tine event object by its ID.
+     *
+     * @param string $eventId Unique ID of event.
+     *
+     * @return stdClass Raw Tine event object.
+     */
+    private function getRawEventById($eventId)
+    {
+        $response = $this->jsonRpc('Calendar.searchEvents', (object) array(
+            'filter' => array(
+                (object) array(
+                    'field' => 'id',
+                    'operator' => 'equals',
+                    'value' => $eventId
+                )
+            ),
+            'paging' => (object) array(
+                'dir' => 'ASC',
+                'limit' => 1, // IDs are unique
+                'start' => 0
+            )
+        ));
+        return $response->result->results[0];
+    }
+
+    /**
+     * Sets the confirmation status for current user on a raw Tine event.
+     *
+     * @param stdClass $rawEvent Raw Tine event object.
+     * @param string   $confirmation User confirmation status: ACCEPTED, DECLINED, NEEDS-ACTION.
+     */
+    private function setRawEventConfirmation(&$rawEvent, $confirmation)
+    {
+        $userMail = $this->tineSession->getAttribute('Expressomail.email');
+
+        $rawEvent->attachments = array();
+        $rawEvent->send = '';
+
+        foreach ($rawEvent->attendee as &$atd) {
+            if (is_object($atd->user_id)) {
+                if ($userMail === $atd->user_id->email) {
+                    $atd->status = $confirmation;
+                    return;
+                }
+            }
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/UpdateMessageCache.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/UpdateMessageCache.php
new file mode 100644 (file)
index 0000000..8e1c996
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for updateMessageCache calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+class UpdateMessageCache extends LiteRequest
+{
+
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $response = $this->jsonRpc('Expressomail.updateMessageCache', (object) array(
+            'folderId' => $this->param('folderId'),
+            'time' => 10 // minutes?
+        ));
+
+        return (object) array(
+            'id' => $response->result->id,
+            'totalMails' => $this->coalesce($response->result, 'cache_totalcount', 0),
+            'unreadMails' => $this->coalesce($response->result, 'cache_unreadcount', 0),
+            'recentMails' => $this->coalesce($response->result, 'cache_recentcount', 0)
+        );
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/UploadTempFile.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/UploadTempFile.php
new file mode 100644 (file)
index 0000000..ea45aa7
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Expresso Lite
+ * Handler for uploadTempFile calls.
+ * Originally avaible in Tine.class (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014-2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request;
+
+use ExpressoLite\Backend\Request\Utils\MessageUtils;
+
+class UploadTempFile extends LiteRequest
+{
+    /**
+     * @see ExpressoLite\Backend\Request\LiteRequest::execute
+     */
+    public function execute()
+    {
+        $fileType = isset($_SERVER['HTTP_X_FILE_TYPE']) ?
+            $_SERVER['HTTP_X_FILE_TYPE'] : 'text/plain';
+
+        if (isset($_SERVER['HTTP_X_FILE_NAME']) && isset($_SERVER['HTTP_X_FILE_TYPE'])) {
+            return MessageUtils::uploadTempFile(
+                $this->tineSession,
+                file_get_contents('php://input'),
+                $_SERVER['HTTP_X_FILE_NAME'],
+                $fileType
+            );
+        } else {
+            return $this->httpError(400, 'Nenhum arquivo a ser carregado.');
+        }
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Utils/MessageUtils.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/Request/Utils/MessageUtils.php
new file mode 100644 (file)
index 0000000..c3c6411
--- /dev/null
@@ -0,0 +1,210 @@
+<?php
+/**
+ * Expresso Lite
+ * Utility class that provides tine mail message facilities that are
+ * shared by several lite request handlers.
+ * These methods were originally avaible in Tine.class
+ * (prior to the backend refactoring).
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Rodrigo Dias <rodrigo.dias@serpro.gov.br>
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014-2015 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend\Request\Utils;
+
+use ExpressoLite\TineTunnel\TineJsonRpc;
+use ExpressoLite\TineTunnel\TineSession;
+use ExpressoLite\TineTunnel\Request;
+
+class MessageUtils
+{
+
+    /**
+     * Adds or clear a flag (read, forwarded, etc...) for all msgs with the informed.
+     *
+     * @param TineSession $tineSession The TineSession used to communicate with Tine.
+     * @param array $msgIds An array with all the messages ids to have the flag set or unset.
+     * @param string $flag The flag to be set or unset.
+     * @param boolean $doAdd Tells if the flags need to be set (true) or unset(false).
+     *
+     * @return The JSON RPC call response given by Tine
+     */
+    public static function addOrClearFlag(TineSession $tineSession, array $msgIds, $flag, $doAdd)
+    {
+        $operation = $doAdd ? 'Expressomail.addFlags' : 'Expressomail.clearFlags';
+
+        if (count($msgIds) === 1) {
+            $filterData = (object) array(
+                'filterData' => $msgIds[0],
+                'flags' => $flag
+            );
+        } else {
+            $filterData = (object) array(
+                'filterData' => array(
+                    (object) array(
+                        'field' => 'id',
+                        'operator' => 'in',
+                        'value' => $msgIds
+                    ),
+                    (object) array(
+                        'field' => 'container_id',
+                        'operator' => 'in',
+                        'value' => array()
+                    )
+                ),
+                'flags' => $flag
+            );
+        }
+
+        $response = $tineSession->jsonRpc($operation, $filterData);
+
+        return $response->result;
+    }
+
+    /**
+     * Creates an stdClass object with all attributes that Tine needs to save a message.
+     *
+     * @param TineSession $tineSession The TineSession used to communicate with Tine.
+     * @param $subject
+     * @param $body
+     * @param array $to Array with all the emails in the "To" field
+     * @param array $cc Array with all the emails in the "Cc" field
+     * @param array $bcc Array with all the emails in the "Bcc" field
+     * @param boolean $isImportant Indicates whether the message should be sent with the Important flag
+     * @param $replyToId
+     * @param $forwardFromId
+     * @param $origDraftId
+     * @param array $attachs
+     *
+     * @return stdClass object with all attributes that Tine needs to save a message
+     */
+    public static function buildMessageForSaving(TineSession $tineSession, $subject, $body, array $to, array $cc, array $bcc,
+        $isImportant = false, $replyToId = null, $forwardFromId = null, $origDraftId = null, array $attachs = array(), $wantConfirm = false)
+    {
+        $recordData = (object) array(
+            'note' => '0',
+            'content_type' => 'text/html',
+            'account_id' => $tineSession->getAttribute('Expressomail.accountId'),
+            'to' => $to,
+            'cc' => $cc,
+            'bcc' => $bcc,
+            'subject' => $subject,
+            'body' => $body,
+            'attachments' => $attachs,
+            'embedded_images' => array(),
+            'from_email' => $tineSession->getAttribute('Expressomail.email'),
+            'from_name' => $tineSession->getAttribute('Expressomail.from'),
+            'customfields' => (object) array(),
+            'add_contacts' => true
+        );
+        if ($isImportant)
+            $recordData->importance = true;
+        if ($replyToId !== null) { // this email is a reply
+            $recordData->original_id = $replyToId;
+            $recordData->flags = "\\Answered";
+        } else
+            if ($forwardFromId !== null) { // this email is being forwarded
+                $recordData->original_id = $forwardFromId;
+                $recordData->flags = 'Passed';
+            }
+        if ($origDraftId !== null) {
+            $recordData->original_id = $origDraftId; // editing an existing draft
+        }
+        if($wantConfirm) {
+            $recordData->reading_conf = true;
+        }
+        return $recordData;
+    }
+
+    /**
+     * Moves messages to a different folder
+     *
+     * @param TineSession $tineSession The TineSession used to communicate with Tine.
+     * @param array $msgIds An array with all the messages ids to be moved.
+     * @param $folderId The destination folder id
+     *
+     * @return The JSON RPC call response given by Tine
+     */
+    public static function moveMessages(TineSession $tineSession, array $msgIds, $folderId)
+    {
+        $response = $tineSession->jsonRpc('Expressomail.moveMessages', (object) array(
+            'filterData' => array(
+                (object) array(
+                    'field' => 'id',
+                    'operator' => 'in',
+                    'value' => $msgIds // simple array
+                ),
+                (object) array(
+                    'field' => 'container_id',
+                    'operator' => 'in',
+                    'value' => array()
+                )
+            ),
+            'targetFolderId' => $folderId
+        ));
+        return $response->result;
+    }
+
+    /**
+     * Gets the binary content of a contact picture
+     *
+     * @param TineSession $tineSession The TineSession used to communicate with Tine.
+     * @param $contactId The id of the contact whose picture we want
+     * @param $creationTime The contact creation time (needed by Tine)
+     * @param int $cx The desired picture width
+     * @param int $cy The desired picture height
+     *
+     * @return The binary content of a contact picture
+     */
+    public static function getContactPicture(TineSession $tineSession, $contactId, $creationTime, $cx, $cy)
+    {
+        //we need to make a 'custom' request to get the contact picture, so
+        //we build a Request object manually instead of relying on tineSession
+        //to do it for us
+        $req = new Request();
+        $req->setBinaryOutput(false); // do not directly output the binary stream to client
+        $req->setCookieHandler($tineSession); //tineSession has the necessary cookies
+
+
+        $req->setUrl($tineSession->getTineUrl() . '?method=Tinebase.getImage&application=Addressbook' . "&location=&id={$contactId}&width={$cx}&height={$cy}&ratiomode=0&mtime={$creationTime}000");
+        $req->setHeaders(array(
+                'Connection: keep-alive',
+                'DNT: 1',
+                'User-Agent: ' . TineJsonRpc::DEFAULT_USERAGENT,
+                'Pragma: no-cache',
+                'Cache-Control: no-cache'
+        ));
+        $mugshot = $req->send();
+        return ($mugshot !== null) ? $mugshot : '';
+    }
+
+    /**
+     * Uploads raw data to Tine as a temp file that may be used as a message attachment.
+     *
+     * @param  TineSession $tineSession     The TineSession used to communicate with Tine.
+     * @param  string      $rawData         The file raw content
+     * @param  string      $fileDisplayName The file name
+     * @param  string      $mimeType        The file's mimetype
+     *
+     * @return string The temp file information.
+     */
+    public static function uploadTempFile(TineSession $tineSession, $rawData, $fileDisplayName, $mimeType)
+    {
+        $req = new Request();
+        $req->setUrl($tineSession->getTineUrl() . '?method=Tinebase.uploadTempFile&eid='.sha1(mt_rand().microtime()));
+        $req->setCookieHandler($tineSession); //tineSession has the necessary cookies
+        $req->setPostFields($rawData);
+        $req->setHeaders(array(
+            'DNT: 1',
+            'User-Agent: ' . TineJsonRpc::DEFAULT_USERAGENT,
+            'X-File-Name: ' . $fileDisplayName,
+            'X-File-Size: 0',
+            'X-File-Type: ' . $mimeType,
+            'X-Requested-With: XMLHttpRequest',
+            'X-Tine20-Request-Type: HTTP'
+        ));
+        return $req->send(REQUEST::POST);
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Backend/TineSessionRepository.php b/clients/ExpressoLite/src/api/ExpressoLite/Backend/TineSessionRepository.php
new file mode 100644 (file)
index 0000000..23bf684
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Expresso Lite
+ * This class is responsible for storing and receiving the TineSession
+ * object associated to the current user session.
+ *
+ * @package   ExpressoLite\Backend
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Backend;
+
+use ExpressoLite\TineTunnel\TineSession;
+
+class TineSessionRepository
+{
+
+    /**
+     * This constant is the default name for the $_SESSION entry that
+     * will store the current TineSession
+     */
+    const TINE_SESSION = 'tine_session';
+
+    /**
+     * @var string $backendUrl Contains the address where tine is located.
+     * May be overriden for test purposes (see overrideBackendUrlForTests)
+     */
+    private static $backendUrl = BACKEND_URL;
+
+    /**
+     * This method allows us to override Tine's URL defined in
+     * conf.php. This is useful for testing. However, this method
+     * should NOT be used in production code.
+     *
+     * @param $backendUrl The new URL.
+     *
+     */
+    public static function overrideBackendUrlForTests($backendUrl)
+    {
+        self::$backendUrl = $backendUrl;
+    }
+
+    /**
+     * Dumps the current tineSession and creates a new one
+     *
+     * @return TineSession The new TineSession
+     *
+     */
+    public static function resetTineSession() {
+        $tineSession = self::createNewTineSession();
+        self::storeTineSession($tineSession);
+        return $tineSession;
+    }
+
+    /**
+     * Creates and inits a new TineSession.
+     *
+     * @return a new TineSession
+     *
+     */
+    private static function createNewTineSession()
+    {
+        $tineSession = new TineSession(self::$backendUrl);
+
+        if (defined('ACTIVATE_TINE_XDEBUG') && ACTIVATE_TINE_XDEBUG === true) {
+            //'=== true' only for the sake of clarity
+            $tineSession->setActivateTineXDebug(true);
+        }
+
+        $tineSession->setLocale('pt_BR');
+        return $tineSession;
+    }
+
+    /**
+     * Returns the current TineSession stored in the user session.
+     * If no TineSession exists, it creates a new one.
+     *
+     * @return the current TineSession
+     *
+     */
+    public static function getTineSession()
+    {
+        if (! isset($_SESSION[self::TINE_SESSION])) {
+            self::resetTineSession();
+        }
+        return $_SESSION[self::TINE_SESSION];
+    }
+
+    /**
+     * Stores a TineSession in the user session ($_SESSION)
+     *
+     * @param a new TineSession
+     *
+     */
+    public static function storeTineSession(TineSession $tineSession)
+    {
+        $_SESSION[self::TINE_SESSION] = $tineSession;
+    }
+}
+
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Exception/CaptchaRequiredException.php b/clients/ExpressoLite/src/api/ExpressoLite/Exception/CaptchaRequiredException.php
new file mode 100644 (file)
index 0000000..0d48c85
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Tine Tunnel
+ * Exception thrown when Tine fails a login because it requires the user
+ * to inform a CAPTCHA.
+ *
+ * @package   ExpressoLite\Exception
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2015 Serpro (http://www.serpro.gov.br)
+ */
+
+namespace ExpressoLite\Exception;
+
+class CaptchaRequiredException extends LiteException
+{
+    /**
+     * @var string $catcha Base 64 encoded CAPTCHA image
+     */
+    private $captcha;
+
+    /**
+     * Creates a new <tt>CaptchaRequiredException</tt>
+     *
+     * @param string $catcha Base 64 encoded CAPTCHA image.
+     */
+    public function __construct($captcha)
+    {
+        parent::__construct('CAPTCHA authentication required', self::HTTP_401_UNAUTHORIZED);
+        $this->captcha = $captcha;
+    }
+
+    /**
+     * @return string $catcha Base 64 encoded CAPTCHA image
+     */
+    public function getCaptcha()
+    {
+        return $this->captcha;
+    }
+}
diff --git a/clients/ExpressoLite/src/api/ExpressoLite/Exception/LiteException.php b/clients/ExpressoLite/src/api/ExpressoLite/Exception/LiteException.php
new file mode 100644 (file)
index 0000000..8a456ce
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Expresso Lite
+ * Represents a generic exception thrown by a ExpressoLite feature
+ *
+ * @package   ExpressoLite\Exception
+ * @license   http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author    Charles Wust <charles.wust@serpro.gov.br>
+ * @copyright Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
+ */
+namespace ExpressoLite\Exception;
+
+use \Exception;
+
+cl