Brand new customer portal - alpha version: requires adjustments to work with various ticketing installation options

SVN:trunk[4044]
This commit is contained in:
Romain Quetiez
2016-05-10 15:04:44 +00:00
parent 242f7785e6
commit a618f2804b
123 changed files with 29248 additions and 2 deletions

View File

@@ -2333,3 +2333,14 @@ span.refresh-button {
}
#setup .module-selection-banner img {
max-height: 48px;
}
#setup .module-selection-body {
height: 28em;
overflow: auto;
border: #cccccc 1px solid;
}

View File

@@ -1762,3 +1762,13 @@ span.refresh-button {
.date_format_tooltip td {
padding: 0.25em;
}
#setup {
.module-selection-banner img {
max-height: 48px;
}
.module-selection-body {
height: 28em;
overflow: auto;
border: #ccc 1px solid;
}
}

View File

@@ -86,6 +86,18 @@
<module>itop-request-mgmt</module>
</modules>
<default>true</default>
<sub_options>
<options type="array">
<choice>
<title>Enhanced Customer Portal</title>
<description>Replace the built-in customer portal with a more modern version, working better with hand-held devices and bringing new features</description>
<modules type="array">
<module>itop-portal</module>
<module>itop-portal-base</module>
</modules>
</choice>
</options>
</sub_options>
</choice>
<choice>
<title>ITIL Compliant Tickets Management</title>
@@ -106,6 +118,14 @@
<module>itop-incident-mgmt-itil</module>
</modules>
</choice>
<choice>
<title>Enhanced Customer Portal</title>
<description>Replace the built-in customer portal with a more modern version, working better with hand-held devices and bringing new features</description>
<modules type="array">
<module>itop-portal</module>
<module>itop-portal-base</module>
</modules>
</choice>
</options>
</sub_options>
</choice>

View File

@@ -599,4 +599,98 @@
<do_search>1</do_search>
</menu>
</menus>
<module_designs>
<module_design id="itop-portal" xsi:type="portal">
<bricks>
<brick id="faq" xsi:type="Combodo\iTop\Portal\Brick\BrowseBrick" _delta="define">
<active>true</active>
<rank>7</rank>
<width>4</width>
<title>Brick:Portal:FAQ:Title</title>
<levels>
<level id="1">
<class>FAQCategory</class>
<parent_att/>
<name_att/>
<tooltip_att/>
<title>Catégories</title>
<actions/>
<levels>
<level id="1">
<class>FAQ</class>
<parent_att>category_id</parent_att>
<name_att>title</name_att>
<tooltip_att>summary</tooltip_att>
<title>FAQs</title>
<fields>
<field id="error_code"/>
<field id="key_words"/>
</fields>
<actions>
<action id="view" xsi:type="view"/>
</actions>
<levels/>
</level>
</levels>
</level>
</levels>
<browse_modes>
<availables>
<mode id="list"/>
<mode id="tree"/>
</availables>
<default>list</default>
</browse_modes>
<data_loading>full</data_loading>
</brick>
</bricks>
<forms>
<form id="faq" _delta="define">
<class>FAQ</class>
<!-- fields tag is optional. If not specified, attributes from zlist "details" will be choose as default -->
<fields>
<field id="category_name"/>
<field id="title"/>
<field id="error_code"/>
<field id="key_words"/>
<field id="summary"/>
<field id="description"/>
</fields>
<twig>
<div class="row">
<div class="col-sm-4">
<div class="form_field" data-field-id="category_name"></div>
<div class="form_field" data-field-id="title"></div>
<div class="form_field" data-field-id="error_code"></div>
<div class="form_field" data-field-id="key_words"></div>
<div class="form_field" data-field-id="summary"></div>
</div>
<div class="col-sm-8">
<div class="form_field" data-field-id="description"></div>
</div>
</div>
</twig>
<modes>
<mode id="view"/>
</modes>
</form>
</forms>
<classes>
<class id="FAQCategory" _delta="define">
<scopes>
<scope id="all">
<oql_view><![CDATA[SELECT FAQCategory]]></oql_view>
</scope>
</scopes>
</class>
<class id="FAQ" _delta="define">
<scopes>
<scope id="all">
<oql_view><![CDATA[SELECT FAQ]]></oql_view>
</scope>
</scopes>
</class>
</classes>
</module_design>
</module_designs>
</itop_design>

View File

@@ -0,0 +1,111 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Portal
Dict::Add('EN US', 'English', 'English', array(
'Page:DefaultTitle' => 'iTop User portal',
'Page:PleaseWait' => 'Please wait...',
'Page:Home' => 'Home',
'Page:GoPortalHome' => 'Home page',
'Page:GoPreviousPage' => 'Previous page',
'Portal:Button:Submit' => 'Submit',
'Portal:Button:Cancel' => 'Cancel',
'Portal:Button:Close' => 'Close',
'Portal:Button:Add' => 'Add',
'Portal:Button:Remove' => 'Remove',
'Portal:Button:Delete' => 'Delete',
'Error:HTTP:404' => 'Page not found',
'Error:HTTP:500' => 'Oops! An error has occured.',
'Error:HTTP:GetHelp' => 'Please contact your iTop administrator if the problem keeps happening.',
'Error:XHR:Fail' => 'Could not load data, please contact your iTop administrator',
'Portal:Datatables:Language:Processing' => 'Please wait...',
'Portal:Datatables:Language:Search' => 'filter :',
'Portal:Datatables:Language:LengthMenu' => 'Display _MENU_ items per page',
'Portal:Datatables:Language:ZeroRecords' => 'No result',
'Portal:Datatables:Language:Info' => 'Page _PAGE_ of _PAGES_',
'Portal:Datatables:Language:InfoEmpty' => 'No information',
'Portal:Datatables:Language:InfoFiltered' => 'filtered out of _MAX_ items',
'Portal:Datatables:Language:EmptyTable' => 'No data available in this table',
'Portal:Datatables:Language:DisplayLength:All' => 'All',
'Portal:Datatables:Language:Paginate:First' => 'First',
'Portal:Datatables:Language:Paginate:Previous' => 'Previous',
'Portal:Datatables:Language:Paginate:Next' => 'Next',
'Portal:Datatables:Language:Paginate:Last' => 'Last',
'Portal:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort',
'Portal:Datatables:Language:Sort:Descending' => 'enable for a descending sort',
'Portal:Autocomplete:NoResult' => 'No data',
'Portal:Attachments:DropZone:Message' => 'Drop your files to add them as attachments',
));
// UserProfile brick
Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:UserProfile:Name' => 'User profile',
'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil' => 'My profile',
'Brick:Portal:UserProfile:Navigation:Dropdown:Logout' => 'Logoff',
'Brick:Portal:UserProfile:Password:Title' => 'Password',
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Choose password',
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Confirm password',
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Personal informations',
'Brick:Portal:UserProfile:Photo:Title' => 'Photo',
));
// BrowseBrick brick
Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Browse:Name' => 'Browse throught items',
'Brick:Portal:Browse:Mode:List' => 'List',
'Brick:Portal:Browse:Mode:Tree' => 'Tree',
'Brick:Portal:Browse:Action:Drilldown' => 'Drilldown',
'Brick:Portal:Browse:Action:View' => 'Details',
'Brick:Portal:Browse:Action:Edit' => 'Edit',
'Brick:Portal:Browse:Action:Create' => 'Create',
'Brick:Portal:Browse:Action:CreateObjectFromThis' => 'New %1$s',
'Brick:Portal:Browse:Tree:ExpandAll' => 'Expand all',
'Brick:Portal:Browse:Tree:CollapseAll' => 'Collapse all',
'Brick:Portal:Browse:Filter:NoData' => 'No item',
));
// ManageBrick brick
Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Manage:Name' => 'Manage items',
'Brick:Portal:Manage:Table:NoData' => 'No item.',
));
// ObjectBrick brick
Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Object:Name' => 'Object',
'Brick:Portal:Object:Form:Create:Title' => 'New %1$s',
'Brick:Portal:Object:Form:Edit:Title' => 'Updating %2$s (%1$s)',
'Brick:Portal:Object:Form:View:Title' => '%1$s : %2$s',
'Brick:Portal:Object:Form:Stimulus:Title' => 'Please, fill the following informations:',
'Brick:Portal:Object:Form:Message:Saved' => 'Saved',
'Brick:Portal:Object:Search:Regular:Title' => 'Select %1$s (%2$s)',
'Brick:Portal:Object:Search:Hierarchy:Title' => 'Select %1$s (%2$s)',
));
// CreateBrick brick
Dict::Add('EN US', 'English', 'English', array(
'Brick:Portal:Create:Name' => 'Quick creation',
));
?>

View File

@@ -0,0 +1,111 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
// Portal
Dict::Add('FR FR', 'French', 'Français', array(
'Page:DefaultTitle' => 'Portail utilisateur iTop',
'Page:PleaseWait' => 'Veuillez patienter...',
'Page:Home' => 'Accueil',
'Page:GoPortalHome' => 'Revenir à l\'accueil',
'Page:GoPreviousPage' => 'Page précédente',
'Portal:Button:Submit' => 'Valider',
'Portal:Button:Cancel' => 'Annuler',
'Portal:Button:Close' => 'Fermer',
'Portal:Button:Add' => 'Ajouter',
'Portal:Button:Remove' => 'Enlever',
'Portal:Button:Delete' => 'Supprimer',
'Error:HTTP:404' => 'Page non trouvée',
'Error:HTTP:500' => 'Oups ! Une erreur est survenue.',
'Error:HTTP:GetHelp' => 'Si le problème persiste, veuillez contacter votre administrateur iTop.',
'Error:XHR:Fail' => 'Impossible de charger les données, veuillez contacter votre administrateur iTop si le problème persiste',
'Portal:Datatables:Language:Processing' => 'Veuillez patienter...',
'Portal:Datatables:Language:Search' => 'Filtrer :',
'Portal:Datatables:Language:LengthMenu' => 'Afficher _MENU_ éléments par page',
'Portal:Datatables:Language:ZeroRecords' => 'Aucun résultat',
'Portal:Datatables:Language:Info' => 'Page _PAGE_ sur _PAGES_',
'Portal:Datatables:Language:InfoEmpty' => 'Pas d\'information disponible',
'Portal:Datatables:Language:InfoFiltered' => 'filtrées sur un total de _MAX_ éléments',
'Portal:Datatables:Language:EmptyTable' => 'Aucune donnée élément à afficher',
'Portal:Datatables:Language:DisplayLength:All' => 'Tout',
'Portal:Datatables:Language:Paginate:First' => 'Premier',
'Portal:Datatables:Language:Paginate:Previous' => 'Précédent',
'Portal:Datatables:Language:Paginate:Next' => 'Suivant',
'Portal:Datatables:Language:Paginate:Last' => 'Dernier',
'Portal:Datatables:Language:Sort:Ascending' => 'activer pour trier la colonne par ordre croissant',
'Portal:Datatables:Language:Sort:Descending' => 'activer pour trier la colonne par ordre décroissant',
'Portal:Autocomplete:NoResult' => 'Aucun résultat',
'Portal:Attachments:DropZone:Message' => 'Déposez vos fichiers pour les ajouter en pièces jointes',
));
// UserProfile brick
Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:UserProfile:Name' => 'Profil utilisateur',
'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil' => 'Mon profil',
'Brick:Portal:UserProfile:Navigation:Dropdown:Logout' => 'Déconnexion',
'Brick:Portal:UserProfile:Password:Title' => 'Mot de passe',
'Brick:Portal:UserProfile:Password:ChoosePassword' => 'Choisissez un mot de passe',
'Brick:Portal:UserProfile:Password:ConfirmPassword' => 'Confirmer le mot de passe',
'Brick:Portal:UserProfile:PersonalInformations:Title' => 'Informations personnelles',
'Brick:Portal:UserProfile:Photo:Title' => 'Photo',
));
// BrowseBrick brick
Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Browse:Name' => 'Navigation dans les éléments',
'Brick:Portal:Browse:Mode:List' => 'Liste',
'Brick:Portal:Browse:Mode:Tree' => 'Hiérarchie',
'Brick:Portal:Browse:Action:Drilldown' => 'Parcourir',
'Brick:Portal:Browse:Action:View' => 'Détails',
'Brick:Portal:Browse:Action:Edit' => 'Modifier',
'Brick:Portal:Browse:Action:Create' => 'Créer',
'Brick:Portal:Browse:Action:CreateObjectFromThis' => 'Créer %1$s',
'Brick:Portal:Browse:Tree:ExpandAll' => 'Tout déplier',
'Brick:Portal:Browse:Tree:CollapseAll' => 'Tout replier',
'Brick:Portal:Browse:Filter:NoData' => 'Aucun élément',
));
// ManageBrick brick
Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Manage:Name' => 'Gestion d\'éléments',
'Brick:Portal:Manage:Table:NoData' => 'Aucun élément',
));
// ObjectBrick brick
Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Object:Name' => 'Objet',
'Brick:Portal:Object:Form:Create:Title' => 'Création de %1$s',
'Brick:Portal:Object:Form:Edit:Title' => 'Modification de %2$s (%1$s)',
'Brick:Portal:Object:Form:View:Title' => '%1$s : %2$s',
'Brick:Portal:Object:Form:Stimulus:Title' => 'Veuillez compléter les informations suivantes :',
'Brick:Portal:Object:Form:Message:Saved' => 'Enregistré',
'Brick:Portal:Object:Search:Regular:Title' => 'Sélection de %1$s (%2$s)',
'Brick:Portal:Object:Search:Hierarchy:Title' => 'Sélection de %1$s (%2$s)',
));
// CreateBrick brick
Dict::Add('FR FR', 'French', 'Français', array(
'Brick:Portal:Create:Name' => 'Création rapide',
));
?>

View File

@@ -0,0 +1,43 @@
<?php
SetupWebPage::AddModule(
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
'itop-portal-base/1.0.0', array(
// Identification
'label' => 'iTop Portal Base',
'category' => 'Portal',
// Setup
'dependencies' => array(
),
'mandatory' => true,
'visible' => false,
// Components
'datamodel' => array(
'portal/src/entities/abstractbrick.class.inc.php',
'portal/src/entities/portalbrick.class.inc.php',
'portal/src/controllers/abstractcontroller.class.inc.php',
'portal/src/controllers/brickcontroller.class.inc.php',
'portal/src/routers/abstractrouter.class.inc.php',
),
'webservice' => array(
//'webservices.itop-portal-base.php',
),
'dictionary' => array(
'fr.dict.itop-portal-base.php',
//'de.dict.itop-portal-base.php',
),
'data.struct' => array(
//'data.struct.itop-portal-base.xml',
),
'data.sample' => array(
//'data.sample.itop-portal-base.xml',
),
// Documentation
'doc.manual_setup' => '',
'doc.more_information' => '',
// Default settings
'settings' => array(
),
)
);
?>

View File

@@ -0,0 +1,35 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
/**
* AbstractController class
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
abstract class AbstractController
{
}
?>

View File

@@ -0,0 +1,30 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
abstract class BrickController extends AbstractController
{
}
?>

View File

@@ -0,0 +1,608 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \Exception;
use \UserRights;
use \Dict;
use \MetaModel;
use \DBSearch;
use \DBObjectSearch;
use \DBObjectSet;
use \DBObject;
use \BinaryExpression;
use \FieldExpression;
use \VariableExpression;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Helper\SecurityHelper;
use \Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
use \Combodo\iTop\Portal\Brick\AbstractBrick;
use \Combodo\iTop\Portal\Brick\BrowseBrick;
class BrowseBrickController extends BrickController
{
const LEVEL_SEPARATOR = '-';
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sBrowseMode = null, $sDataLoading = null)
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
// Getting availables browse modes
$aBrowseModes = $oBrick->GetAvailablesBrowseModes();
$aBrowseButtons = array_keys($aBrowseModes);
// Getting current browse mode (First from router pamater, then default brick value)
$sBrowseMode = (!empty($sBrowseMode)) ? $sBrowseMode : $oBrick->GetDefaultBrowseMode();
// Getting current dataloading mode (First from router parameter, then query parameter, then default brick value)
$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : ( ($oRequest->query->get('sDataLoading') !== null) ? $oRequest->query->get('sDataLoading') : $oBrick->GetDataLoading() );
// Getting search value
$sSearchValue = $oRequest->get('sSearchValue', null);
$aData = array();
$aLevelsProperties = array();
$aLevelsClasses = array();
$this->TreeToFlatLevelsProperties($oApp, $oBrick->GetLevels(), $aLevelsProperties);
// Concistency checks
if (!in_array($sBrowseMode, array_keys($aBrowseModes)))
{
$oApp->abort(500, 'Browse brick "' . $sBrickId . '" : Unknown browse mode "' . $sBrowseMode . '", availables are ' . implode(' / ', array_keys($aBrowseModes)));
}
if (empty($aLevelsProperties))
{
$oApp->abort(500, 'Browse brick "' . $sBrickId . '" : No levels to display.');
}
// Building DBobjectSearch
$oQuery = null;
// ... In this case only we have to build a specific query for the current level only
if (($sBrowseMode === BrowseBrick::ENUM_BROWSE_MODE_TREE) && ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY))
{
// Will be handled later in the pagination part
}
// .. Otherwise
else
{
// We iterate (in reverse mode /!\) over the levels to build the whole query, starting from the bottom
$aLevelsPropertiesKeys = array_keys($aLevelsProperties);
$iLoopMax = count($aLevelsPropertiesKeys) - 1;
$oFullBinExpr = null;
for ($i = $iLoopMax; $i >= 0; $i--)
{
// Retrieving class alias for all depth
array_unshift($aLevelsClasses, $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetClassAlias());
// Joining queries from bottom-up
if ($i < $iLoopMax)
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search'] = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->Join($aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['search'], DBSearch::JOIN_REFERENCED_BY, $aLevelsProperties[$aLevelsPropertiesKeys[$i + 1]]['parent_att']);
}
// Adding search clause
// Note : For know the search is naive and looks only for the exact match. It doesn't search for words separately
if ($sSearchValue !== null)
{
// - Cleaning the search value by exploding and trimming spaces
$aSearchValues = explode(' ', $sSearchValue);
array_walk($aSearchValues, function(&$sSearchValue, $sKey)
{
trim($sSearchValue);
});
// - Building query for the search values parts
$oLevelBinExpr = null;
$iSearchLoopMax = count($aSearchValues) - 1;
for ($j = 0; $j <= $iSearchLoopMax; $j++)
{
$oSearchBinExpr = new BinaryExpression(new FieldExpression($aLevelsProperties[$aLevelsPropertiesKeys[$i]]['name_att'], $aLevelsPropertiesKeys[$i]), 'LIKE', new VariableExpression('search_value_' . $j));
if ($j === 0)
{
$oLevelBinExpr = $oSearchBinExpr;
}
else
{
$oLevelBinExpr = new BinaryExpression($oLevelBinExpr, 'AND', $oSearchBinExpr);
}
if ($j === $iSearchLoopMax)
{
}
}
// - Building query for the level
if ($i === $iLoopMax)
{
$oFullBinExpr = $oLevelBinExpr;
}
else
{
$oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oLevelBinExpr);
}
// - Adding it to the query when complete
if ($i === 0)
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->AddConditionExpression($oFullBinExpr);
}
}
// Setting selected classes and binding parameters
if ($i === 0)
{
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetSelectedClasses($aLevelsClasses);
if ($sSearchValue !== null)
{
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
$aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams();
// Note : $iSearchloopMax was initialized on the previous loop
for ($j = 0; $j <= $iSearchLoopMax; $j++)
{
$aQueryParams['search_value_' . $j] = '%' . $aSearchValues[$j] . '%';
}
$aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetInternalParams($aQueryParams);
}
}
}
$oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search'];
// Testing appropriate data loading mode if we are in auto
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO)
{
// - Check how many records there is.
// - Update $sDataLoading with its new value regarding the number of record and the threshold
$oCountSet = new DBObjectSet($oQuery);
$fThreshold = (float) MetaModel::GetModuleSetting($oApp['combodo.portal.instance.id'], 'lazy_loading_threshold');
$sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL;
unset($oCountSet);
}
}
// Setting query pagination if needed
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY)
{
switch ($sBrowseMode)
{
case BrowseBrick::ENUM_BROWSE_MODE_LIST:
// Retrieving parameters
$iPageNumber = (int) $oRequest->get('iPageNumber', 1);
$iCountPerPage = (int) $oRequest->get('iCountPerPage', BrowseBrick::DEFAULT_COUNT_PER_PAGE_LIST);
// Getting total records number
$oCountSet = new DBObjectSet($oQuery);
$aData['recordsTotal'] = $oCountSet->Count();
$aData['recordsFiltered'] = $oCountSet->Count();
unset($oCountSet);
$oSet = new DBObjectSet($oQuery);
$oSet->SetLimit($iCountPerPage, $iCountPerPage * ($iPageNumber - 1));
break;
case BrowseBrick::ENUM_BROWSE_MODE_TREE:
// Retrieving parameters
$sLevelAlias = $oRequest->get('sLevelAlias');
$sNodeId = $oRequest->get('sNodeId');
// If no values for those parameters, we might be loading page in lazy mode for the first time, therefore the URL doesn't have those informations.
if (empty($sLevelAlias))
{
reset($aLevelsProperties);
$oQuery = $aLevelsProperties[key($aLevelsProperties)]['search'];
if (!empty($sNodeId))
{
$oQuery->AddCondition('id', $sNodeId);
}
}
// Else we need to find the OQL for that particular level
else
{
$bFoundLevel = false;
foreach ($aLevelsProperties as $aLevelProperties)
{
if ($aLevelProperties['alias'] === $sLevelAlias)
{
if (isset($aLevelProperties['levels']) && !empty($aLevelProperties['levels']) && isset($aLevelsProperties[$aLevelProperties['levels'][0]]))
{
$oQuery = $aLevelsProperties[$aLevelProperties['levels'][0]]['search'];
if (!empty($sNodeId))
{
$oQuery->AddCondition($aLevelsProperties[$aLevelProperties['levels'][0]]['parent_att'], $sNodeId);
}
$bFoundLevel = true;
break;
}
}
}
if (!$bFoundLevel)
{
$oApp->abort(500, 'Browse brick "' . $sBrickId . '" : Level alias "' . $sLevelAlias . '" is not defined for that brick.');
}
}
$oSet = new DBObjectSet($oQuery);
break;
default:
// We should never be there. If there is an other browse mode for that brick :
// - If it's from a custom brick extension, it should be handle by the extension router/controller
// - If it's from a base brick, it should be handle in a case above this one
// - If none of the previous statements was done, this fail safe will load all data as it's not able to know how to handle the pagination
$oSet = new DBObjectSet($oQuery);
break;
}
}
else
{
$oSet = new DBObjectSet($oQuery);
}
// Retrieving results and organizing them for templating
$aItems = array();
while ($aCurrentRow = $oSet->FetchAssoc())
{
switch ($sBrowseMode)
{
case BrowseBrick::ENUM_BROWSE_MODE_TREE:
$this->AddToTreeItems($aItems, $aCurrentRow, $aLevelsProperties);
break;
case BrowseBrick::ENUM_BROWSE_MODE_LIST:
default:
$aItems[] = $this->AddToFlatItems($aCurrentRow, $aLevelsProperties);
break;
}
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
$aData = $aData + array(
'data' => $aItems,
'levelsProperties' => $aLevelsProperties
);
$oResponse = $oApp->json($aData);
}
else
{
$aData = $aData + array(
'oBrick' => $oBrick,
'sBrickId' => $sBrickId,
'sBrowseMode' => $sBrowseMode,
'aBrowseButtons' => $aBrowseButtons,
'sDataLoading' => $sDataLoading,
'aItems' => json_encode($aItems),
'iItemsCount' => count($aItems),
'aLevelsProperties' => json_encode($aLevelsProperties)
);
// Note : To extend this brick's template, depending on what you want to do :
// a) Modify the whole template :
// - Create a template and specify it in the brick configuration
// b) Add a new browse mode :
// - Create a template for that browse mode,
// - Add the mode to those availables in the brick configuration,
// - Create a router and add a route for the new browse mode
if ($oBrick->GetPageTemplatePath() !== null)
{
$sTemplatePath = $oBrick->GetPageTemplatePath();
}
else
{
$sTemplatePath = $aBrowseModes[$sBrowseMode]['template'];
}
$oResponse = $oApp['twig']->render($sTemplatePath, $aData);
}
return $oResponse;
}
/**
* Flattens the $aLevels into $aLevelsProperties in order to be able to build an OQL query from multiple single queries related to each others.
* As of now it only keeps search / parent_att / name_att properties.
*
* Note : This is not in the BrowseBrick class because the classes should not rely on DBObjectSearch.
*
* @param Silex\Application $oApp
* @param array $aLevels Levels from a BrowseBrick class
* @param array $aLevelsProperties Reference to an array that will contain the flattened levels
* @param string $sLevelAliasPrefix String that will be prefixed to the level ID as an unique path identifier
*/
protected function TreeToFlatLevelsProperties(Application $oApp, array $aLevels, array &$aLevelsProperties, $sLevelAliasPrefix = 'L')
{
foreach ($aLevels as $aLevel)
{
$sCurrentLevelAlias = $sLevelAliasPrefix . static::LEVEL_SEPARATOR . $aLevel['id'];
$oSearch = DBSearch::CloneWithAlias(DBSearch::FromOQL($aLevel['oql']), $sCurrentLevelAlias);
// Restricting to the allowed scope
$oScopeSearch = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $oSearch->GetClass(), UR_ACTION_READ);
$oSearch = ($oScopeSearch !== null) ? $oSearch->Intersect($oScopeSearch) : null;
if ($oSearch !== null)
{
$aLevelsProperties[$sCurrentLevelAlias] = array(
'alias' => $sCurrentLevelAlias,
'title' => ($aLevel['title'] !== null) ? Dict::S($aLevel['title']) : MetaModel::GetName($oSearch->GetClass()),
'parent_att' => $aLevel['parent_att'],
'name_att' => $aLevel['name_att'],
'tooltip_att' => $aLevel['tooltip_att'],
'search' => $oSearch,
'fields' => array(),
'actions' => array()
);
// Adding current level's fields
if (isset($aLevel['fields']))
{
$aLevelsProperties[$sCurrentLevelAlias]['fields'] = array();
foreach ($aLevel['fields'] as $sFieldAttCode)
{
$aLevelsProperties[$sCurrentLevelAlias]['fields'][] = array(
'code' => $sFieldAttCode,
'label' => MetaModel::GetAttributeDef($oSearch->GetClass(), $sFieldAttCode)->GetLabel()
);
}
}
// Flattening and adding sublevels
if (isset($aLevel['levels']))
{
foreach ($aLevel['levels'] as $aChildLevel)
{
// Checking if the sublevel if allowed
$oChildSearch = DBSearch::FromOQL($aChildLevel['oql']);
if (SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oChildSearch->GetClass()))
{
// Adding the sublevel to this one
$aLevelsProperties[$sCurrentLevelAlias]['levels'][] = $sCurrentLevelAlias . static::LEVEL_SEPARATOR . $aChildLevel['id'];
// Adding drilldown action if necessary
foreach ($aLevel['actions'] as $sId => $aAction)
{
if ($aAction['type'] === BrowseBrick::ENUM_ACTION_DRILLDOWN)
{
$aLevelsProperties[$sCurrentLevelAlias]['actions'][$sId] = $aAction;
break;
}
}
}
unset($oChildSearch);
}
$this->TreeToFlatLevelsProperties($oApp, $aLevel['levels'], $aLevelsProperties, $sCurrentLevelAlias);
}
// Adding actions to the level
foreach ($aLevel['actions'] as $sId => $aAction)
{
// ... Only if it's not already there (eg. the drilldown added with the sublevels)
if (!array_key_exists($sId, $aLevelsProperties[$sCurrentLevelAlias]['actions']))
{
// Adding action only if allowed
if (($aAction['type'] === BrowseBrick::ENUM_ACTION_VIEW) && !SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oSearch->GetClass()))
{
continue;
}
elseif (($aAction['type'] === BrowseBrick::ENUM_ACTION_EDIT) && !SecurityHelper::IsActionAllowed($oApp, UR_ACTION_MODIFY, $oSearch->GetClass()))
{
continue;
}
elseif ($aAction['type'] === BrowseBrick::ENUM_ACTION_DRILLDOWN)
{
continue;
}
// Setting action title
if (isset($aAction['title']))
{
// Note : There could be an enhancement here, by checking if the string code has the '%1' needle and use Dict::S or Dict::Format accordingly.
// But it would require to benchmark a potential performance drop as it will be done for all items
$aAction['title'] = Dict::S($aAction['title']);
}
else
{
switch ($aAction['type'])
{
case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS:
// We can only make translate a dictionnary entry with a class placeholder when the action has a class tag. if it has a factory method, we don't know yet what class is going to be created
if ($aAction['factory']['type'] === BrowseBrick::ENUM_FACTORY_TYPE_CLASS)
{
$aAction['title'] = Dict::Format('Brick:Portal:Browse:Action:CreateObjectFromThis', MetaModel::GetName($aAction['factory']['value']));
$aAction['url'] = $oApp['url_generator']->generate('p_object_create', array('sObjectClass' => $aAction['factory']['value']));
}
else
{
$aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Create');
}
break;
case BrowseBrick::ENUM_ACTION_VIEW:
$aAction['title'] = Dict::S('Brick:Portal:Browse:Action:View');
break;
case BrowseBrick::ENUM_ACTION_EDIT:
$aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Edit');
break;
case BrowseBrick::ENUM_ACTION_DRILLDOWN:
$aAction['title'] = Dict::S('Brick:Portal:Browse:Action:Drilldown');
break;
}
}
// Setting action url
switch ($aAction['type'])
{
case BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS:
if ($aAction['factory']['type'] === BrowseBrick::ENUM_FACTORY_TYPE_CLASS)
{
$aAction['url'] = $oApp['url_generator']->generate('p_object_create', array('sObjectClass' => $aAction['factory']['value']));
}
else
{
$aAction['url'] = $oApp['url_generator']->generate('p_object_create_from_factory', array('sEncodedMethodName' => base64_encode($aAction['factory']['value']), 'sObjectClass' => '-objectClass-', 'sObjectId' => '-objectId-'));
}
break;
}
$aLevelsProperties[$sCurrentLevelAlias]['actions'][$sId] = $aAction;
}
}
}
}
}
/**
* Prepares the action rules for an $oItem. Action rules are used to defined some iTopObjectCopier rules that will be apply to an DBObject created from $oItem
*
* @param DBObject $oItem
* @param string $sLevelsAlias
* @param array $aLevelsProperties
* @return array
*/
protected function PrepareActionRulesForItem(DBObject $oItem, $sLevelsAlias, array &$aLevelsProperties)
{
$aActionRules = array();
foreach ($aLevelsProperties[$sLevelsAlias]['actions'] as $sId => $aAction)
{
$aActionRules[$sId] = ContextManipulatorHelper::EncodeRulesToken($aAction['rules'], array($oItem));
}
return $aActionRules;
}
/**
* Takes $aCurrentRow as a flat array and transform it in another flat array (not objects) with only the necessary informations
*
* eg:
* - $aCurrentRow : array('L-1' => ObjectClass1, 'L-1-1' => ObjectClass2, 'L-1-1-1' => ObjectClass3)
* - $aRow will be : array(
* 'L1' => array(
* 'name' => 'Object class 1 name'
* ),
* 'L1-1' => array(
* 'name' => 'Object class 2 name',
* ),
* 'L1-1-1' => array(
* 'name' => 'Object class 3 name',
* ),
* ...
* )
*
* @param array $aCurrentRow
* @param array $aLevelsProperties
* @return array
*/
protected function AddToFlatItems(array $aCurrentRow, array &$aLevelsProperties)
{
$aRow = array();
foreach ($aCurrentRow as $key => $value)
{
$aRow[$key] = array(
'level_alias' => $key,
'id' => $value->GetKey(),
'name' => $value->Get($aLevelsProperties[$key]['name_att']),
'class' => get_class($value),
'action_rules_token' => $this->PrepareActionRulesForItem($value, $key, $aLevelsProperties)
);
// Adding tooltip attribute if necessary
if ($aLevelsProperties[$key]['tooltip_att'] !== null)
{
$aRow[$key]['tooltip'] = $value->Get($aLevelsProperties[$key]['tooltip_att']);
}
// Adding fields attributes if necessary
if (!empty($aLevelsProperties[$key]['fields']))
{
$aRow[$key]['fields'] = array();
foreach ($aLevelsProperties[$key]['fields'] as $aField)
{
$aRow[$key]['fields'][$aField['code']] = $value->Get($aField['code']);
}
}
}
return $aRow;
}
/**
* Takes $aCurrentRow as a flat array to recursvily convert and insert it into a tree array $aItems.
* This is used to build a tree array from a DBObjectSet retrieved with FetchAssoc().
*
* eg:
* - $aCurrentRow : array('L-1' => ObjectClass1, 'L-1-1' => ObjectClass2, 'L-1-1-1' => ObjectClass3)
* - $aItems will be : array(
* 'L1' =>
* 'name' => 'Object class 1 name',
* 'subitems' => array(
* 'L1-1' => array(
* 'name' => 'Object class 2 name',
* 'subitems' => array(
* 'L1-1-1' => array(
* 'name' => 'Object class 3 name',
* 'subitems' => array()
* ),
* ...
* )
* ),
* ...
* )
* ),
* ...
* )
*
* @param array &$aItems Reference to the array to be built
* @param array $aCurrentRow
* @param array $aLevelsProperties
*/
protected function AddToTreeItems(array &$aItems, array $aCurrentRow, array &$aLevelsProperties)
{
$aCurrentRowKeys = array_keys($aCurrentRow);
$aCurrentRowValues = array_values($aCurrentRow);
$sCurrentIndex = $aCurrentRowKeys[0] . '::' . $aCurrentRowValues[0]->GetKey();
if (!isset($aItems[$sCurrentIndex]))
{
$aItems[$sCurrentIndex] = array(
'level_alias' => $aCurrentRowKeys[0],
'id' => $aCurrentRowValues[0]->GetKey(),
'name' => $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['name_att']),
'class' => get_class($aCurrentRowValues[0]),
'subitems' => array(),
'action_rules_token' => $this->PrepareActionRulesForItem($aCurrentRowValues[0], $aCurrentRowKeys[0], $aLevelsProperties)
);
if ($aLevelsProperties[$aCurrentRowKeys[0]]['tooltip_att'] !== null)
{
$aItems[$sCurrentIndex]['tooltip'] = $aCurrentRowValues[0]->Get($aLevelsProperties[$aCurrentRowKeys[0]]['tooltip_att']);
}
}
$aCurrentRowSliced = array_slice($aCurrentRow, 1);
if (!empty($aCurrentRowSliced))
{
$this->AddToTreeItems($aItems[$sCurrentIndex]['subitems'], $aCurrentRowSliced, $aLevelsProperties);
}
}
}
?>

View File

@@ -0,0 +1,58 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpKernel\HttpKernelInterface;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
class CreateBrickController extends BrickController
{
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId)
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
$aRouteParams = array(
'sObjectClass' => $oBrick->GetClass()
);
// Preparing redirection route
// - Checking for action rules
$aRules = $oBrick->GetRules();
if (!empty($aRules))
{
$aRouteParams['ar_token'] = ContextManipulatorHelper::EncodeRulesToken($aRules);
}
// - Adding brick id to the params
$aRouteParams['sBrickId'] = $sBrickId;
// - Generating route
$sRedirectRoute = $oApp['url_generator']->generate('p_object_create', $aRouteParams);
// - Request
$oSubRequest = Request::create($sRedirectRoute, 'GET', $oRequest->query->all(), $oRequest->cookies->all(), array(), $oRequest->server->all());
return $oApp->handle($oSubRequest, HttpKernelInterface::SUB_REQUEST, true);
}
}
?>

View File

@@ -0,0 +1,38 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
class DefaultController
{
public function homeAction(Request $oRequest, Application $oApp)
{
$aData = array();
$template = $oApp['combodo.portal.instance.conf']['properties']['templates']['home'];
return $oApp['twig']->render($template, $aData);
}
}
?>

View File

@@ -0,0 +1,418 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \UserRights;
use \CMDBSource;
use \MetaModel;
use \AttributeDefinition;
use \AttributeDate;
use \AttributeDateTime;
use \DBSearch;
use \DBObjectSearch;
use \DBObjectSet;
use \FieldExpression;
use \BinaryExpression;
use \VariableExpression;
use \SQLExpression;
use \UnaryExpression;
use \Dict;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Helper\SecurityHelper;
use \Combodo\iTop\Portal\Brick\AbstractBrick;
use \Combodo\iTop\Portal\Brick\ManageBrick;
class ManageBrickController extends BrickController
{
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId, $sGroupingTab, $sDataLoading = null)
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
$aData = array();
$aGroupingTabsValues = array();
$aGroupingAreasValues = array();
$aQueries = array();
// Getting current dataloading mode (First from router parameter, then query parameter, then default brick value)
$sDataLoading = ($sDataLoading !== null) ? $sDataLoading : ( ($oRequest->query->get('sDataLoading') !== null) ? $oRequest->query->get('sDataLoading') : $oBrick->GetDataLoading() );
// Getting search value
$sSearchValue = $oRequest->get('sSearchValue', null);
// Starting to build query
$oQuery = DBSearch::FromOQL($oBrick->GetOql());
// - Adding search clause if necessary
// Note : This is a very naive search at the moment
if ($sSearchValue !== null)
{
$aSearchListItems = MetaModel::GetZListItems($oQuery->GetClass(), 'standard_search');
$oFullBinExpr = null;
for ($i = 0; $i < count($aSearchListItems); $i++)
{
$sSearchItemAttr = $aSearchListItems[$i];
$oBinExpr = new BinaryExpression(new FieldExpression($sSearchItemAttr, $oQuery->GetClassAlias()), 'LIKE', new VariableExpression('search_value'));
// At each iteration we build the complete expression for the search like ( (field1 LIKE %search%) OR (field2 LIKE %search%) OR (field3 LIKE %search%) ...)
if ($i === 0)
{
$oFullBinExpr = $oBinExpr;
}
else
{
$oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oBinExpr);
}
// Then on the last iteration we add the complete expression to the query
// Note : We don't do it after the loop as there could be an empty search ZList
if ($i === (count($aSearchListItems) - 1))
{
// - Adding expression to the query
$oQuery->AddConditionExpression($oFullBinExpr);
// - Setting expression parameters
// Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb
$aQueryParams = $oQuery->GetInternalParams();
$aQueryParams['search_value'] = '%' . $sSearchValue . '%';
$oQuery->SetInternalParams($aQueryParams);
}
}
}
// Preparing tabs
// - We need to retrieve distinct values for the grouping attribute
if ($oBrick->HasGroupingTabs())
{
$aGroupingTabs = $oBrick->GetGroupingTabs();
// If tabs are made of the distinct values of an attribute, we have a find them via a query
if ($oBrick->IsGroupingTabsByDistinctValues())
{
$sGroupingTabAttCode = $aGroupingTabs['attribute'];
$oDistinctQuery = DBSearch::FromOQL($oBrick->GetOql());
$oFieldExp = new FieldExpression($sGroupingTabAttCode, $oDistinctQuery->GetClassAlias());
$sDistinctSql = $oDistinctQuery->MakeGroupByQuery(array(), array('grouped_by_1' => $oFieldExp), true);
$aDistinctResults = CMDBSource::QueryToArray($sDistinctSql);
if (!empty($aDistinctResults))
{
foreach ($aDistinctResults as $aDistinctResult)
{
$oConditionQuery = DBSearch::CloneWithAlias($oQuery, 'GTAB');
$oExpression = new BinaryExpression(new FieldExpression($sGroupingTabAttCode, $oDistinctQuery->GetClassAlias()), '=', new UnaryExpression($aDistinctResult['grouped_by_1']));
$oConditionQuery->AddConditionExpression($oExpression);
$aGroupingTabsValues[$aDistinctResult['grouped_by_1']] = array(
'value' => $aDistinctResult['grouped_by_1'],
'label' => strip_tags($oFieldExp->MakeValueLabel($oDistinctQuery, $aDistinctResult['grouped_by_1'], '')),
'condition' => $oConditionQuery,
'count' => $aDistinctResult['_itop_count_']
);
unset($oConditionQuery);
}
unset($aDistinctResults);
}
else
{
$aGroupingTabsValues['undefined'] = array(
'value' => 'undefined',
'label' => '',
'condition' => null,
'count' => null
);
}
}
// Otherwise we create the tabs from the SQL expressions
else
{
foreach ($aGroupingTabs['groups'] as $aGroup)
{
$aGroupingTabsValues[$aGroup['id']] = array(
'value' => $aGroup['id'],
'label' => Dict::S($aGroup['title']),
'condition' => DBSearch::FromOQL($aGroup['condition']),
'count' => null
);
}
}
}
// - Retrieving the current grouping tab to display and altering the query to do so
if ($sGroupingTab === null)
{
if ($oBrick->HasGroupingTabs())
{
reset($aGroupingTabsValues);
$sGroupingTab = key($aGroupingTabsValues);
if ($aGroupingTabsValues[$sGroupingTab]['condition'] !== null)
{
$oQuery = $oQuery->Intersect($aGroupingTabsValues[$sGroupingTab]['condition']);
}
}
else
{
// Do not group by tabs, display all in the same page
}
}
else
{
if ($aGroupingTabsValues[$sGroupingTab]['condition'] !== null)
{
$oQuery = $oQuery->Intersect($aGroupingTabsValues[$sGroupingTab]['condition']);
}
}
// Preparing areas
// - We need to retrieve distinct values for the grouping attribute
// Note : Will have to be changed when we consider grouping on something else than the finalclass
$sParentAlias = $oQuery->GetClassAlias();
if (true)
{
$sGroupingAreaAttCode = 'finalclass';
// For root classes
if (MetaModel::IsValidAttCode($oQuery->GetClass(), $sGroupingAreaAttCode))
{
$oDistinctQuery = DBSearch::FromOQL($oBrick->GetOql());
$oFieldExp = new FieldExpression($sGroupingAreaAttCode, $sParentAlias);
$sDistinctSql = $oDistinctQuery->MakeGroupByQuery(array(), array('grouped_by_1' => $oFieldExp), true);
$aDistinctResults = CMDBSource::QueryToArray($sDistinctSql);
foreach ($aDistinctResults as $aDistinctResult)
{
$oConditionQuery = DBSearch::CloneWithAlias($oQuery, 'GARE');
$oExpression = new BinaryExpression(new FieldExpression($sGroupingAreaAttCode, 'GARE'), '=', new UnaryExpression($aDistinctResult['grouped_by_1']));
$oConditionQuery->AddConditionExpression($oExpression);
$aGroupingAreasValues[$aDistinctResult['grouped_by_1']] = array(
'value' => $aDistinctResult['grouped_by_1'],
'label' => MetaModel::GetName($aDistinctResult['grouped_by_1']), // Caution : This works only because we froze the grouping areas on the finalclass attribute.
'condition' => $oConditionQuery,
'count' => $aDistinctResult['_itop_count_']
);
unset($oConditionQuery);
}
unset($aDistinctResults);
}
// For leaf classes
else
{
$aGroupingAreasValues[$oQuery->GetClass()] = array(
'value' => $oQuery->GetClass(),
'label' => MetaModel::GetName($oQuery->GetClass()), // Caution : This works only because we froze the grouping areas on the finalclass attribute.
'condition' => null,
'count' => 0
);
}
}
// - Retrieving the grouping areas to display
$sGroupingArea = $oRequest->get('sGroupingArea');
// - If specified or lazy loading, we trunc the $aGroupingAreasValues to keep only this one
if ($sGroupingArea !== null)
{
$aGroupingAreasValues = array($sGroupingArea => $aGroupingAreasValues[$sGroupingArea]);
}
// - Preapring the queries
foreach ($aGroupingAreasValues as $sKey => $aGroupingAreasValue)
{
$oAreaQuery = DBSearch::CloneWithAlias($oQuery, $sParentAlias);
if ($aGroupingAreasValue['condition'] !== null)
{
//$oAreaQuery->AddConditionExpression($aGroupingAreasValue['condition']);
$oAreaQuery = $oAreaQuery->Intersect($aGroupingAreasValue['condition']);
}
// Restricting query to allowed scope on each classes
// Note : Will need to moved the scope restriction on queries elsewhere when we consider grouping on something else than finalclass
$oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $aGroupingAreasValue['value'], UR_ACTION_MODIFY);
$oAreaQuery = ($oScopeQuery !== null) ? $oAreaQuery->Intersect($oScopeQuery) : null;
$aQueries[$sKey] = $oAreaQuery;
}
// Testing appropriate data loading mode if we are in auto
// - For all (html) tables, this doesn't care for the grouping ares (finalclass)
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO)
{
// - Check how many records there is.
// - Update $sDataLoading with its new value regarding the number of record and the threshold
$oCountSet = new DBObjectSet($oQuery);
$fThreshold = (float) MetaModel::GetModuleSetting($oApp['combodo.portal.instance.id'], 'lazy_loading_threshold');
$sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL;
unset($oCountSet);
}
// Preparing data sets
$aSets = array();
foreach ($aQueries as $sKey => $oQuery)
{
// Checking if we have a valid query
if ($oQuery !== null)
{
// Setting query pagination if needed
if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_LAZY)
{
// Retrieving parameters
$iPageNumber = (int) $oRequest->get('iPageNumber', 1);
$iCountPerPage = (int) $oRequest->get('iCountPerPage', ManageBrick::DEFAULT_COUNT_PER_PAGE_LIST);
// Getting total records number
$oCountSet = new DBObjectSet($oQuery);
$aData['recordsTotal'] = $oCountSet->Count();
$aData['recordsFiltered'] = $oCountSet->Count();
unset($oCountSet);
$oSet = new DBObjectSet($oQuery);
$oSet->SetLimit($iCountPerPage, $iCountPerPage * ($iPageNumber - 1));
}
else
{
$oSet = new DBObjectSet($oQuery);
}
$aSets[$sKey] = $oSet;
}
}
// Retrieving and preparing datas for rendering
$aGroupingAreasData = array();
foreach ($aSets as $sKey => $oSet)
{
// Set properties
$sCurrentClass = $sKey;
$sTitleAttrCode = MetaModel::GetFriendlyNameAttributeCode($sCurrentClass);
// Getting area columns properties
$aColumnsAttrs = $oBrick->GetFields();
// Adding friendlyname attribute to the list is not already in it
if (!in_array($sTitleAttrCode, $aColumnsAttrs))
{
$aColumnsAttrs = array_merge(array($sTitleAttrCode), $aColumnsAttrs);
}
// Loading columns definition
$aColumnsDefinition = array();
foreach ($aColumnsAttrs as $sColumnAttr)
{
$oAttDef = MetaModel::GetAttributeDef($sKey, $sColumnAttr);
$aColumnsDefinition[$sColumnAttr] = array(
'title' => $oAttDef->GetLabel(),
'type' => ($oAttDef instanceof AttributeDateTime) ? 'moment-'.$oAttDef->GetFormat()->ToMomentJS() : 'html', // Special sorting for Date & Time
);
}
// Getting items
$aItems = array();
// ... For each item
while ($oCurrentRow = $oSet->Fetch())
{
// ... Retrieving item's attributes values
$aItemAttrs = array();
foreach ($aColumnsAttrs as $sItemAttr)
{
$aActions = array();
// Set the edit action to the main attribute only
if ($sItemAttr === $sTitleAttrCode)
{
$aActions[] = array(
'type' => ManageBrick::ENUM_ACTION_EDIT,
'class' => $sCurrentClass,
'id' => $oCurrentRow->GetKey()
);
}
$oAttDef = MetaModel::GetAttributeDef($sCurrentClass, $sItemAttr);
if ($oAttDef->IsExternalKey())
{
$sValue = $oCurrentRow->Get($sItemAttr . '_friendlyname');
// Adding a view action on the external keys
if ($oCurrentRow->Get($sItemAttr) !== $oAttDef->GetNullValue())
{
// Checking if we can view the object
if ((SecurityHelper::IsActionAllowed($oApp, UR_ACTION_READ, $oAttDef->GetTargetClass(), $oCurrentRow->Get($sItemAttr))))
{
$aActions[] = array(
'type' => ManageBrick::ENUM_ACTION_VIEW,
'class' => $oAttDef->GetTargetClass(),
'id' => $oCurrentRow->Get($sItemAttr)
);
}
}
}
else
{
$sValue = $oAttDef->GetValueLabel($oCurrentRow->Get($sItemAttr));
}
unset($oAttDef);
$aItemAttrs[$sItemAttr] = array(
'att_code' => $sItemAttr,
'value' => $sValue,
'actions' => $aActions
);
}
// ... And item's properties
$aItems[] = array(
'id' => $oCurrentRow->GetKey(),
'class' => $sCurrentClass,
'attributes' => $aItemAttrs
);
}
$aGroupingAreasData[$sKey] = array(
'sId' => $sKey,
'sTitle' => $aGroupingAreasValues[$sKey]['label'],
'aItems' => $aItems,
'iItemsCount' => $oSet->Count(),
'aColumnsDefinition' => $aColumnsDefinition
);
}
// Preparing response
if ($oRequest->isXmlHttpRequest())
{
$aData = $aData + array(
'data' => $aGroupingAreasData[$sGroupingArea]['aItems']
);
$oResponse = $oApp->json($aData);
}
else
{
$aData = $aData + array(
'oBrick' => $oBrick,
'sBrickId' => $sBrickId,
'sGroupingTab' => $sGroupingTab,
'aGroupingTabsValues' => $aGroupingTabsValues,
'sDataLoading' => $sDataLoading,
'aGroupingAreasData' => $aGroupingAreasData,
'sDateFormat' => AttributeDate::GetFormat()->ToMomentJS(),
'sDateTimeFormat' => AttributeDateTime::GetFormat()->ToMomentJS(),
);
$oResponse = $oApp['twig']->render($oBrick->GetPageTemplatePath(), $aData);
}
return $oResponse;
}
}
?>

View File

@@ -0,0 +1,78 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Controller;
use \UserRights;
use \Silex\Application;
use \Symfony\Component\HttpFoundation\Request;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
use \Combodo\iTop\Portal\Brick\UserProfileBrick;
use \Combodo\iTop\Portal\Controller\ObjectController;
class UserProfileBrickController extends BrickController
{
public function DisplayAction(Request $oRequest, Application $oApp, $sBrickId)
{
// If the brick id was not specified, we get the first one registered that is an instance of UserProfileBrick as default
if ($sBrickId === null)
{
foreach ($oApp['combodo.portal.instance.conf']['bricks'] as $oTmpBrick)
{
if ($oTmpBrick instanceof UserProfileBrick)
{
$oBrick = $oTmpBrick;
}
}
// We make sure a UserProfileBrick was found
if (!isset($oBrick) || $oBrick === null)
{
$oBrick = new UserProfileBrick();
//$oApp->abort(500, 'UserProfileBrick : Brick could not be loaded as there was no UserProfileBrick loaded in the application.');
}
}
else
{
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $sBrickId);
}
$aData = array();
// Retrieving current contact
$oCurContact = UserRights::GetContactObject();
$sCurContactClass = get_class($oCurContact);
$sCurContactId = $oCurContact->GetKey();
// Preparing contact form
$aData['forms']['contact'] = ObjectController::HandleForm($oRequest, $oApp, ObjectController::ENUM_MODE_EDIT, $sCurContactClass, $sCurContactId);
// var_dump($aData['forms']['contact']);
// die();
$aData = $aData + array(
'oBrick' => $oBrick
);
return $oApp['twig']->render($oBrick->GetPageTemplatePath(), $aData);
}
}
?>

View File

@@ -0,0 +1,524 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Brick;
require_once APPROOT . '/core/moduledesign.class.inc.php';
require_once APPROOT . '/setup/compiler.class.inc.php';
use \DOMXPath;
use \DOMFormatException;
use \ModuleDesign;
use \Combodo\iTop\DesignElement;
/**
* Description of AbstractBrick
*
* Bricks are used mostly in the portal for now, not the console.
* This class defines common functionnalities for the extended classes.
*
* @author Guillaume Lajarige
*/
abstract class AbstractBrick
{
const ENUM_DATA_LOADING_LAZY = 'lazy';
const ENUM_DATA_LOADING_FULL = 'full';
const ENUM_DATA_LOADING_AUTO = 'auto';
const DEFAULT_MANDATORY = true;
const DEFAULT_ACTIVE = true;
const DEFAULT_RANK = 1.0;
const DEFAULT_PAGE_TEMPLATE_PATH = null;
const DEFAULT_TITLE = '';
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_AUTO;
const DEFAULT_ALLOWED_PROFILES_OQL = '';
const DEFAULT_DENIED_PROFILES_OQL = '';
protected $sId;
protected $bMandatory;
protected $bActive;
protected $fRank;
protected $sPageTemplatePath;
protected $sTitle;
protected $sDataLoading;
protected $aAllowedProfiles;
protected $aDeniedProfiles;
protected $sAllowedProfilesOql;
protected $sDeniedProfilesOql;
/**
* Returns all enum values for the data loading modes in an array.
*
* @return array
*/
static function GetEnumDataLoadingValues()
{
return array(self::ENUM_DATA_LOADING_LAZY, self::ENUM_DATA_LOADING_FULL, self::ENUM_DATA_LOADING_AUTO);
}
/**
* Default attributes values of AbstractBrick are specified in the definition, not the constructor.
*/
function __construct()
{
$this->bMandatory = static::DEFAULT_MANDATORY;
$this->bActive = static::DEFAULT_ACTIVE;
$this->fRank = static::DEFAULT_RANK;
$this->sPageTemplatePath = static::DEFAULT_PAGE_TEMPLATE_PATH;
$this->sTitle = static::DEFAULT_TITLE;
$this->sDataLoading = static::DEFAULT_DATA_LOADING;
$this->aAllowedProfiles = array();
$this->aDeniedProfiles = array();
$this->sAllowedProfilesOql = static::DEFAULT_ALLOWED_PROFILES_OQL;
$this->sDeniedProfilesOql = static::DEFAULT_DENIED_PROFILES_OQL;
}
/**
* Returns the brick id
*
* @return string
*/
public function GetId()
{
return $this->sId;
}
/**
* Returns if brick is mandatory
*
* @return boolean
*/
public function GetMandatory()
{
return $this->bMandatory;
}
/**
* Returns if brick is active
*
* @return boolean
*/
public function GetActive()
{
return $this->bActive;
}
/**
* Returns the brick rank
*
* @return float
*/
public function GetRank()
{
return $this->fRank;
}
/**
* Returns the brick page template path
*
* @return string
*/
public function GetPageTemplatePath()
{
return $this->sPageTemplatePath;
}
/**
* Returns the brick title
*
* @return string
*/
public function GetTitle()
{
return $this->sTitle;
}
/**
* Returns the brick data loading mode
*
* @return string
*/
public function GetDataLoading()
{
return $this->sDataLoading;
}
/**
* Returns allowed profiles for the brick
*
* @return array
*/
public function GetAllowedProfiles()
{
return $this->aAllowedProfiles;
}
/**
* Returns denied profiles for the brick
*
* @return array
*/
public function GetDeniedProfiles()
{
return $this->aDeniedProfiles;
}
/**
* Returns allowed profiles oql query for the brick
*
* @return string
*/
public function GetAllowedProfilesOql()
{
return $this->sAllowedProfilesOql;
}
/**
* Returns denied profiles oql query for the brick
*
* @return string
*/
public function GetDeniedProfilesOql()
{
return $this->sDeniedProfilesOql;
}
/**
* Sets the brick id
*
* @param string $sid
*/
public function SetId($sId)
{
$this->sId = $sId;
return $this;
}
/**
* Sets if the brick is mandatory
*
* @param boolean $bMandatory
*/
public function SetMandatory($bMandatory)
{
$this->bMandatory = $bMandatory;
return $this;
}
/**
* Sets if the brick is active
*
* @param boolean $bActive
*/
public function SetActive($bActive)
{
$this->bActive = $bActive;
return $this;
}
/**
* Sets the rank of the brick
*
* @param float $fRank
*/
public function SetRank($fRank)
{
$this->fRank = $fRank;
return $this;
}
/**
* Sets the page template path of the brick
*
* @param string $sPageTemplatePath
*/
public function SetPageTemplatePath($sPageTemplatePath)
{
$this->sPageTemplatePath = $sPageTemplatePath;
return $this;
}
/**
* Sets the title of the brick
*
* @param string $sTitle
*/
public function SetTitle($sTitle)
{
$this->sTitle = $sTitle;
return $this;
}
/**
* Sets the data loading mode of the brick
*
* @param string $sDataLoading
*/
public function SetDataLoading($sDataLoading)
{
$this->sDataLoading = $sDataLoading;
return $this;
}
/**
* Sets the allowed profiles for the brick
*
* @param array $aAllowedProfiles
*/
public function SetAllowedProfiles($aAllowedProfiles)
{
$this->aAllowedProfiles = $aAllowedProfiles;
return $this;
}
/**
* Sets the denied profiles for the brick
*
* @param array $aDeniedProfiles
*/
public function SetDeniedProfiles($aDeniedProfiles)
{
$this->aDeniedProfiles = $aDeniedProfiles;
return $this;
}
/**
* Sets the allowed profiles oql query for the brick
*
* @param string $sAllowedProfilesOql
*/
public function SetAllowedProfilesOql($sAllowedProfilesOql)
{
$this->sAllowedProfilesOql = $sAllowedProfilesOql;
return $this;
}
/**
* Sets the denied profiles oql query for the brick
*
* @param array $sDeniedProfilesOql
*/
public function SetDeniedProfilesOql($sDeniedProfilesOql)
{
$this->sDeniedProfilesOql = $sDeniedProfilesOql;
return $this;
}
/**
* Adds $sProfile to the list of allowed profiles for that brick
*
* @param string $sProfile
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function AddAllowedProfile($sProfile)
{
$this->aAllowedProfiles[] = $sProfile;
return $this;
}
/**
* Removes $sProfile from the list of allowed profiles
*
* @param string $sProfile
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function RemoveAllowedProfile($sProfile)
{
if (isset($this->aAllowedProfiles[$sProfile]))
{
unset($this->aAllowedProfiles[$sProfile]);
}
return $this;
}
/**
* Returns true if the brick has allowed profiles defined, else false
*
* @return boolean
*/
public function HasAllowedProfiles()
{
return !empty($this->aAllowedProfiles);
}
/**
* Adds $sProfile to the list of denied profiles for that brick
*
* @param string $sProfile
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function AddDeniedProfile($sProfile)
{
$this->aDeniedProfiles[] = $sProfile;
return $this;
}
/**
* Removes $sProfile from the list of denied profiles
*
* @param string $sProfile
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function RemoveDeniedProfile($sProfile)
{
if (isset($this->aDeniedProfiles[$sProfile]))
{
unset($this->aDeniedProfiles[$sProfile]);
}
return $this;
}
/**
* Returns true if the brick has denied profiles defined, else false
*
* @return boolean
*/
public function HasDeniedProfiles()
{
return !empty($this->aDeniedProfiles);
}
/**
* Returns true if the $sProfile is granted.
*
* Meaning that $sProfile is in $aAllowedProfiles and is not in $aDeniedProfiles.
* Priority is deny/allow
*
* @param string $sProfile
* @return boolean
*/
public function IsGrantedForProfile($sProfile)
{
return $this->IsGrantedForProfiles(array($sProfile));
}
/**
* Returns true if the $aProfiles are granted.
*
* Meaning that $aProfiles are in $aAllowedProfiles and are not in $aDeniedProfiles.
* Priority is deny/allow
*
* @param array $aProfiles
* @return boolean
*/
public function IsGrantedForProfiles($aProfiles)
{
$bGranted = true;
if ($this->HasAllowedProfiles())
{
// We set $bGranted to false as the user must explicitly have an allowed profile to be granted
$bGranted = false;
foreach ($aProfiles as $sProfile)
{
if (in_array($sProfile, $this->aAllowedProfiles))
{
$bGranted = true;
break;
}
}
}
if ($this->HasDeniedProfiles())
{
foreach ($aProfiles as $sProfile)
{
if (in_array($sProfile, $this->aDeniedProfiles))
{
$bGranted = false;
break;
}
}
}
return $bGranted;
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return AbstractBrick
* @throws DOMFormatException
*/
public function LoadFromXml(DesignElement $oMDElement)
{
// Checking mandatory elements
if (!$oMDElement->hasAttribute('id'))
{
throw new DOMFormatException('Brick node must have both id and xsi:type attributes defined', null, null, $oMDElement);
}
$this->SetId($oMDElement->getAttribute('id'));
// Checking others elements
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
{
switch ($oBrickSubNode->nodeName)
{
case 'mandatory':
$this->SetMandatory(($oBrickSubNode->GetText() === 'no') ? false : true );
break;
case 'active':
$this->SetActive(($oBrickSubNode->GetText() === 'false') ? false : true );
break;
case 'rank':
$this->SetRank((float) $oBrickSubNode->GetText(static::DEFAULT_RANK));
break;
case 'templates':
$oTemplateNodeList = $oBrickSubNode->GetNodes('template[@id=' . ModuleDesign::XPathQuote('page') . ']');
if ($oTemplateNodeList->length > 0)
{
$this->SetPageTemplatePath($oTemplateNodeList->item(0)->GetText(static::DEFAULT_PAGE_TEMPLATE_PATH));
}
break;
case 'title':
$this->SetTitle($oBrickSubNode->GetText(static::DEFAULT_TITLE));
break;
case 'data_loading':
$this->SetDataLoading($oBrickSubNode->GetText(static::DEFAULT_DATA_LOADING));
break;
case 'security':
foreach ($oBrickSubNode->childNodes as $oSecurityNode)
{
if ($oSecurityNode->nodeType === XML_TEXT_NODE && $oSecurityNode->GetText() === '')
{
throw new DOMFormatException('Brick security node "' . $oSecurityNode->nodeName . '" must contain an OQL query, it cannot be empty', null, null, $oMDElement);
}
switch ($oSecurityNode->nodeName)
{
case 'denied_profiles':
$this->SetDeniedProfilesOql($oSecurityNode->GetText());
break;
case 'allowed_profiles':
$this->SetAllowedProfilesOql($oSecurityNode->GetText());
break;
}
}
break;
}
}
return $this;
}
}
?>

View File

@@ -0,0 +1,444 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Brick;
use DOMFormatException;
use \Combodo\iTop\DesignElement;
use \Combodo\iTop\Portal\Brick\PortalBrick;
/**
* Description of BrowseBrick
*
* @author Guillaume Lajarige
*/
class BrowseBrick extends PortalBrick
{
const ENUM_BROWSE_MODE_LIST = 'list';
const ENUM_BROWSE_MODE_TREE = 'tree';
const ENUM_ACTION_VIEW = 'view';
const ENUM_ACTION_EDIT = 'edit';
const ENUM_ACTION_DRILLDOWN = 'drilldown';
const ENUM_ACTION_CREATE_FROM_THIS = 'create_from_this';
const ENUM_FACTORY_TYPE_METHOD = 'method';
const ENUM_FACTORY_TYPE_CLASS = 'class';
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_FULL;
const DEFAULT_LEVEL_NAME_ATT = 'name';
const DEFAULT_BROWSE_MODE = self::ENUM_BROWSE_MODE_LIST;
const DEFAULT_ACTION = self::ENUM_ACTION_DRILLDOWN;
const DEFAULT_COUNT_PER_PAGE_LIST = 20;
static $sRouteName = 'p_browse_brick';
protected $aLevels;
protected $aAvailablesBrowseModes;
protected $sDefaultBrowseMode;
public function __construct()
{
parent::__construct();
$this->aLevels = array();
$this->aAvailablesBrowseModes = array();
$this->sDefaultBrowseMode = static::DEFAULT_BROWSE_MODE;
}
/**
* Returns the brick levels
*
* @return array
*/
public function GetLevels()
{
return $this->aLevels;
}
/**
* Returns the brick availables browse modes
*
* @return array
*/
public function GetAvailablesBrowseModes()
{
return $this->aAvailablesBrowseModes;
}
/**
* Returns the brick default browse mode
*
* @return string
*/
public function GetDefaultBrowseMode()
{
return $this->sDefaultBrowseMode;
}
/**
* Sets the levels of the brick
*
* @param array $aLevels
*/
public function SetLevels($aLevels)
{
$this->aLevels = $aLevels;
return $this;
}
/**
* Sets the availables browse modes of the brick
*
* @param array $aAvailablesBrowseModes
*/
public function SetAvailablesBrowseModes($aAvailablesBrowseModes)
{
$this->aAvailablesBrowseModes = $aAvailablesBrowseModes;
return $this;
}
/**
* Sets the adefault browse mode of the brick
*
* @param string $sDefaultBrowseMode
*/
public function SetDefaultBrowseMode($sDefaultBrowseMode)
{
$this->sDefaultBrowseMode = $sDefaultBrowseMode;
return $this;
}
/**
* Returns true if the brick has levels
*
* @return boolean
*/
public function HasLevels()
{
return !empty($this->aLevels);
}
/**
* Adds $aLevel to the list of levels for that brick
*
* @param array $aLevel
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function AddLevel($aLevel)
{
$this->aLevels[] = $aLevel;
return $this;
}
/**
* Removes $aLevel from the list of levels browse modes
*
* @param array $aLevel
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function RemoveLevels($aLevel)
{
if (isset($this->aLevels[$aLevel]))
{
unset($this->aLevels[$aLevel]);
}
return $this;
}
/**
* Adds $sModeId to the list of availables browse modes for that brick
*
* @param string $sModeId
* @param array $aData Hash array containing 'template' => TEMPLATE_PATH
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function AddAvailableBrowseMode($sModeId, $aData = array())
{
$this->aAvailablesBrowseModes[$sModeId] = $aData;
return $this;
}
/**
* Removes $sModeId from the list of availables browse modes
*
* @param string $sModeId
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
*/
public function RemoveAvailableBrowseMode($sModeId)
{
if (isset($this->aAvailablesBrowseModes[$sModeId]))
{
unset($this->aAvailablesBrowseModes[$sModeId]);
}
return $this;
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return BrowseBrick
* @throws DOMFormatException
*/
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
// Checking specific elements
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
{
switch ($oBrickSubNode->nodeName)
{
case 'levels':
foreach ($oBrickSubNode->childNodes as $oLevelNode)
{
if ($oLevelNode->nodeName === 'level')
{
$this->AddLevel($this->LoadLevelFromXml($oLevelNode));
}
}
break;
case 'browse_modes':
foreach ($oBrickSubNode->childNodes as $oBrowseModeNode)
{
switch ($oBrowseModeNode->nodeName)
{
case 'availables':
foreach ($oBrowseModeNode->childNodes as $oModeNode)
{
if (!$oModeNode->hasAttribute('id'))
{
throw new DOMFormatException('BrowseBrick : Browse mode must have a unique ID attribute', null, null, $oModeNode);
}
$sModeId = $oModeNode->getAttribute('id');
$aModeData = array();
// Checking if the browse mode has a specific template
$oTemplateNode = $oModeNode->GetOptionalElement('template');
if (($oTemplateNode !== null) && ($oTemplateNode->GetText() !== null))
{
$sTemplatePath = $oTemplateNode->GetText();
}
else
{
$sTemplatePath = 'itop-portal-base/portal/src/views/bricks/browse/mode_' . $sModeId . '.html.twig';
}
$aModeData['template'] = $sTemplatePath;
$this->AddAvailableBrowseMode($sModeId, $aModeData);
}
break;
case 'default':
$this->SetDefaultBrowseMode($oBrowseModeNode->GetText(static::DEFAULT_BROWSE_MODE));
break;
}
}
break;
}
}
// Checking that the brick has at least a browse mode
if (count($this->GetAvailablesBrowseModes()) === 0)
{
throw new DOMFormatException('BrowseBrick : Must have at least one browse mode', null, null, $oMDElement);
}
// Checking that default browse mode in among the availables
if (!in_array($this->sDefaultBrowseMode, array_keys($this->aAvailablesBrowseModes)))
{
throw new DOMFormatException('BrowseBrick : Default browse mode "' . $this->sDefaultBrowseMode . '" must be one of the available browse modes (' . implode(', ', $this->aAvailablesBrowseModes) . ')', null, null, $oMDElement);
}
// Checking that the brick has at least a level
if (count($this->GetLevels()) === 0)
{
throw new DOMFormatException('BrowseBrick : Must have at least one level', null, null, $oMDElement);
}
return $this;
}
/**
* Parses the ModuleDesignElement to recursivly load levels
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return array
* @throws DOMFormatException
*/
protected function LoadLevelFromXml(DesignElement $oMDElement)
{
$aLevel = array(
'parent_att' => null,
'tooltip_att' => null,
'title' => null,
'name_att' => static::DEFAULT_LEVEL_NAME_ATT,
'fields' => array(),
'actions' => array('default' => array('type' => static::DEFAULT_ACTION, 'rules' => array()))
);
// Getting level ID
if ($oMDElement->hasAttribute('id') && $oMDElement->getAttribute('id') !== '')
{
$aLevel['id'] = $oMDElement->getAttribute('id');
}
else
{
throw new DOMFormatException('BrowseBrick : level tag without "id" attribute. It must have one and it must not be empty', null, null, $oMDElement);
}
// Getting level properties
foreach ($oMDElement->childNodes as $oLevelPropertyNode)
{
switch ($oLevelPropertyNode->nodeName)
{
case 'class':
$sClass = $oLevelPropertyNode->GetText();
if ($sClass === '')
{
throw new DOMFormatException('BrowseBrick : class tag is empty. Must contain Classname', null, null, $oLevelPropertyNode);
}
$aLevel['oql'] = 'SELECT ' . $sClass;
break;
case 'oql':
$sOql = $oLevelPropertyNode->GetText();
if ($sOql === '')
{
throw new DOMFormatException('BrowseBrick : oql tag is empty. Must contain OQL statement', null, null, $oLevelPropertyNode);
}
$aLevel['oql'] = $sOql;
break;
case 'parent_att':
case 'tooltip_att':
case 'title':
$aLevel[$oLevelPropertyNode->nodeName] = $oLevelPropertyNode->GetText(null);
break;
case 'name_att':
$aLevel[$oLevelPropertyNode->nodeName] = $oLevelPropertyNode->GetText(static::DEFAULT_LEVEL_NAME_ATT);
break;
case 'fields':
$sTagName = $oLevelPropertyNode->nodeName;
if ($oLevelPropertyNode->hasChildNodes())
{
$aLevel[$sTagName] = array();
foreach ($oLevelPropertyNode->childNodes as $oFieldNode)
{
if ($oFieldNode->hasAttribute('id') && $oFieldNode->getAttribute('id') !== '')
{
$aLevel[$sTagName][] = $oFieldNode->getAttribute('id');
}
else
{
throw new DOMFormatException('BrowseBrick : ' . $sTagName . '/* tag must have an "id" attribute and it must not be empty', null, null, $oFieldNode);
}
}
}
break;
case 'actions':
$sTagName = $oLevelPropertyNode->nodeName;
if ($oLevelPropertyNode->hasChildNodes())
{
$aLevel[$sTagName] = array();
foreach ($oLevelPropertyNode->childNodes as $oActionNode)
{
if ($oActionNode->hasAttribute('id') && $oActionNode->getAttribute('id') !== '')
{
$aTmpAction = array(
'type' => null,
'rules' => array()
);
// Action type
$aTmpAction['type'] = ($oActionNode->hasAttribute('xsi:type') && $oActionNode->getAttribute('xsi:type') !== '') ? $oActionNode->getAttribute('xsi:type') : static::DEFAULT_ACTION;
// Action destination class
if ($aTmpAction['type'] === static::ENUM_ACTION_CREATE_FROM_THIS)
{
if ($oActionNode->GetOptionalElement('factory_method') !== null)
{
$aTmpAction['factory'] = array(
'type' => static::ENUM_FACTORY_TYPE_METHOD,
'value' => $oActionNode->GetOptionalElement('factory_method')->GetText()
);
}
else
{
$aTmpAction['factory'] = array(
'type' => static::ENUM_FACTORY_TYPE_CLASS,
'value' => $oActionNode->GetUniqueElement('class')->GetText()
);
}
}
// Action title
$oActionTitleNode = $oActionNode->GetOptionalElement('title');
if ($oActionTitleNode !== null)
{
$aTmpAction['title'] = $oActionTitleNode->GetText();
}
// Action rules
foreach ($oActionNode->GetNodes('./rules/rule') as $oRuleNode)
{
if ($oRuleNode->hasAttribute('id') && $oRuleNode->getAttribute('id') !== '')
{
$aTmpAction['rules'][] = $oRuleNode->getAttribute('id');
}
else
{
throw new DOMFormatException('BrowseBrick : ' . $sTagName . '/rules/rule tag must have an "id" attribute and it must not be empty', null, null, $oRuleNode);
}
}
$aLevel[$sTagName][$oActionNode->getAttribute('id')] = $aTmpAction;
}
else
{
throw new DOMFormatException('BrowseBrick : ' . $sTagName . '/* tag must have an "id" attribute and it must not be empty', null, null, $oActionNode);
}
}
}
break;
case 'levels':
foreach ($oLevelPropertyNode->childNodes as $oSubLevelNode)
{
if ($oSubLevelNode->nodeName === 'level')
{
$aLevel['levels'][] = $this->LoadLevelFromXml($oSubLevelNode);
}
}
break;
}
}
// Checking if level has an oql
if (!isset($aLevel['oql']) || $aLevel['oql'] === '')
{
throw new DOMFormatException('BrowseBrick : must have a valid <class|oql> tag', null, null, $oMDElement);
}
return $aLevel;
}
}
?>

View File

@@ -0,0 +1,133 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Brick;
use \DOMFormatException;
use \Combodo\iTop\DesignElement;
use \Combodo\iTop\Portal\Brick\PortalBrick;
/**
* Description of CreateBrick
*
* @author Guillaume Lajarige
*/
class CreateBrick extends PortalBrick
{
const DEFAULT_CLASS = '';
static $sRouteName = 'p_create_brick';
protected $sClass;
protected $aRules;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->sClass = static::DEFAULT_CLASS;
$this->aRules = array();
}
/**
* Returns the brick class
*
* @return string
*/
public function GetClass()
{
return $this->sClass;
}
/**
* Sets the class of the brick
*
* @param string $sClass
*/
public function SetClass($sClass)
{
$this->sClass = $sClass;
return $this;
}
/**
* Returns the brick rules
*
* @return array
*/
public function GetRules()
{
return $this->aRules;
}
/**
* Sets the rules of the brick
*
* @param array $aRules
*/
public function SetRules($aRules)
{
$this->aRules = $aRules;
return $this;
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return CreateBrick
*/
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
// Checking specific elements
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
{
switch ($oBrickSubNode->nodeName)
{
case 'class':
$this->SetClass($oBrickSubNode->GetText(self::DEFAULT_CLASS));
break;
case 'rules':
foreach ($oBrickSubNode->childNodes as $oRuleNode)
{
if ($oRuleNode->hasAttribute('id') && $oRuleNode->getAttribute('id') !== '')
{
$this->aRules[] = $oRuleNode->getAttribute('id');
}
else
{
throw new DOMFormatException('CreateBrick: /rules/rule tag must have an "id" attribute and it must not be empty', null, null, $oRuleNode);
}
}
break;
}
}
return $this;
}
}
?>

View File

@@ -0,0 +1,392 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Brick;
use \Combodo\iTop\DesignElement;
use \Combodo\iTop\Portal\Brick\PortalBrick;
use DOMFormatException;
use DBSearch;
use MetaModel;
/**
* Description of ManageBrick
*
* @author Guillaume Lajarige
*/
class ManageBrick extends PortalBrick
{
const ENUM_ACTION_VIEW = 'view';
const ENUM_ACTION_EDIT = 'edit';
const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/manage/layout.html.twig';
const DEFAULT_OQL = '';
const DEFAULT_DATA_LOADING = self::ENUM_DATA_LOADING_LAZY;
const DEFAULT_COUNT_PER_PAGE_LIST = 20;
const DEFAULT_ZLIST_FIELDS = 'list';
static $sRouteName = 'p_manage_brick';
protected $sOql;
protected $aGrouping;
protected $aFields;
public function __construct()
{
parent::__construct();
$this->sOql = static::DEFAULT_OQL;
$this->aGrouping = array();
$this->aFields = array();
// This is hardcoded for now, we might allow area grouping on another attribute in the futur
$this->AddGrouping('areas', array('attribute' => 'finalclass'));
}
/**
* Returns the brick oql
*
* @return string
*/
public function GetOql()
{
return $this->sOql;
}
/**
* Returns the brick grouping
*
* @return array
*/
public function GetGrouping()
{
return $this->aGrouping;
}
/**
* Returns the brick fields to display in the table
*
* @return array
*/
public function GetFields()
{
return $this->aFields;
}
/**
* Sets the oql of the brick
*
* @param string $sOql
*/
public function SetOql($sOql)
{
$this->sOql = $sOql;
return $this;
}
/**
* Sets the grouping of the brick
*
* @param array $aGrouping
*/
public function SetGrouping($aGrouping)
{
$this->aGrouping = $aGrouping;
return $this;
}
/**
* Sets the fields of the brick
*
* @param array $aFields
*/
public function SetFields($aFields)
{
$this->aFields = $aFields;
return $this;
}
/**
* Adds a grouping.
*
* Grouping "tabs" must be of form array("attribute" => value)
*
* @param string $sName (Must be "tabs" or -Not implemented yet, implicit grouping on y axis-)
* @param array $aGrouping
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function AddGrouping($sName, $aGrouping)
{
$this->aGrouping[$sName] = $aGrouping;
// Sorting
if (!$this->IsGroupingByDistinctValues($sName))
{
usort($this->aGrouping[$sName]['groups'], function($a, $b)
{
return $a['rank'] > $b['rank'];
});
}
return $this;
}
/**
* Removes a grouping by its name
*
* @param string $sName
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function RemoveGrouping($sName)
{
if (isset($this->aGrouping[$sName]))
{
unset($this->aGrouping[$sName]);
}
return $this;
}
/**
* Adds a field to display from its attribute_code.
*
* @param string $sAttCode
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function AddField($sAttCode)
{
if (!in_array($sAttCode, $this->aFields))
{
$this->aFields[] = $sAttCode;
}
return $this;
}
/**
* Removes a field
*
* @param string $sAttCode
* @return \Combodo\iTop\Portal\Brick\ManageBrick
*/
public function RemoveField($sAttCode)
{
if (isset($this->aFields[$sAttCode]))
{
unset($this->aFields[$sAttCode]);
}
return $this;
}
/**
* Returns if the brick has grouping tabs or not.
*
* @return boolean
*/
public function HasGroupingTabs()
{
return (isset($this->aGrouping['tabs']) && !empty($this->aGrouping['tabs']));
}
/**
* Returns the grouping tabs properties if exists, else returns false.
*
* @return mixed false if there is no grouping named 'tabs', otherwise the array
*/
public function GetGroupingTabs()
{
return (isset($this->aGrouping['tabs'])) ? $this->aGrouping['tabs'] : false;
}
/**
* Returns if the brick has grouping areas or not.
*
* @return boolean
*/
public function HasGroupingAreas()
{
return (isset($this->aGrouping['areas']) && !empty($this->aGrouping['areas']));
}
/**
* Returns the grouping areas properties if exists, else returns false.
*
* @return mixed false if there is no grouping named 'areas', otherwise the array
*/
public function GetGroupingAreas()
{
return (isset($this->aGrouping['areas'])) ? $this->aGrouping['areas'] : false;
}
/**
* Returns true is the groupings $sGroupingName properties exists and is of the form attribute => attribute_code.
* This is supposed to be called by the IsGroupingTabsByDistinctValues / IsGroupingAreasByDistinctValues function.
*
* @param string $sGroupingName
* @return boolean
*/
public function IsGroupingByDistinctValues($sGroupingName)
{
return (isset($this->aGrouping[$sGroupingName]) && isset($this->aGrouping[$sGroupingName]['attribute']) && $this->aGrouping[$sGroupingName]['attribute'] !== '');
}
/**
* Returns true is the groupings tabs properties exists and is of the form attribute => attribute_code.
* This is mostly used to know if the tabs are grouped by attribute distinct values or by meta-groups (eg : status in ('accepted', 'opened')).
*
* @return boolean
*/
public function IsGroupingTabsByDistinctValues()
{
return $this->IsGroupingByDistinctValues('tabs');
}
/**
* Returns true is the groupings areas properties exists and is of the form attribute => attribute_code.
* This is mostly used to know if the areas are grouped by attribute distinct values or by meta-groups (eg : finalclass in ('Server', 'Router')).
*
* @return boolean
*/
public function IsGroupingAreasByDistinctValues()
{
return $this->IsGroupingByDistinctValues('areas');
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return ManageBrick
*/
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
// Checking specific elements
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
{
switch ($oBrickSubNode->nodeName)
{
case 'class':
$sClass = $oBrickSubNode->GetText();
if ($sClass === '')
{
throw new DOMFormatException('BrowseBrick : class tag is empty. Must contain Classname', null, null, $oBrickSubNode);
}
$this->SetOql('SELECT ' . $sClass);
break;
case 'oql':
$sOql = $oBrickSubNode->GetText();
if ($sOql === '')
{
throw new DOMFormatException('BrowseBrick : oql tag is empty. Must contain OQL statement', null, null, $oBrickSubNode);
}
$this->SetOql($sOql);
break;
case 'fields':
foreach ($oBrickSubNode->GetNodes('./field') as $oFieldNode)
{
if (!$oFieldNode->hasAttribute('id'))
{
throw new DOMFormatException('ManageBrick : Field must have a unique ID attribute', null, null, $oFieldNode);
}
$this->AddField($oFieldNode->getAttribute('id'));
}
break;
case 'grouping':
// Tabs grouping
foreach ($oBrickSubNode->GetNodes('./tabs/*') as $oGroupingNode)
{
switch ($oGroupingNode->nodeName)
{
case 'attribute':
$sAttribute = $oGroupingNode->GetText();
if ($sAttribute !== '')
{
$this->AddGrouping('tabs', array('attribute' => $sAttribute));
}
break;
case 'groups':
$aGroups = array();
foreach ($oGroupingNode->GetNodes('./group') as $oGroupNode)
{
if (!$oGroupNode->hasAttribute('id'))
{
throw new DOMFormatException('ManageBrick : Group must have a unique ID attribute', null, null, $oGroupNode);
}
$sGroupId = $oGroupNode->getAttribute('id');
$aGroup = array();
$aGroup['id'] = $sGroupId; // We don't put the group id as the $aGroups key because the array will be sorted later in AddGrouping, which replace array keys by integer ordered keys
foreach ($oGroupNode->childNodes as $oGroupProperty)
{
switch ($oGroupProperty->nodeName)
{
case 'rank':
$aGroup[$oGroupProperty->nodeName] = (int) $oGroupProperty->GetText(0);
break;
case 'title':
case 'condition':
$aGroup[$oGroupProperty->nodeName] = $oGroupProperty->GetText();
break;
}
}
// Checking constitancy
if (!isset($aGroup['title']) || $aGroup['title'] === '')
{
throw new DOMFormatException('ManageBrick : Group must have a title tag and it must not be empty', null, null, $oGroupNode);
}
if (!isset($aGroup['condition']) || $aGroup['condition'] === '')
{
throw new DOMFormatException('ManageBrick : Group must have a condition tag and it must not be empty', null, null, $oGroupNode);
}
$aGroups[] = $aGroup;
}
$this->AddGrouping('tabs', array('groups' => $aGroups));
break;
}
}
break;
}
}
// Checking if has an oql
if ($this->GetOql() === '')
{
throw new DOMFormatException('BrowseBrick : must have a valid <class|oql> tag', null, null, $oMDElement);
}
// Checking if specified fields, if not we put those from the details zlist
if (empty($this->aFields))
{
$sClass = DBSearch::FromOQL($this->GetOql());
$aFields = MetaModel::FlattenZList(MetaModel::GetZListItems($sClass->GetClass(), static::DEFAULT_ZLIST_FIELDS));
$this->SetFields($aFields);
}
return $this;
}
}
?>

View File

@@ -0,0 +1,243 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Brick;
use \ModuleDesign;
use \Combodo\iTop\DesignElement;
use \Combodo\iTop\Portal\Brick\AbstractBrick;
/**
* Description of PortalBrick
*
* Classes that will be used only in the portal, not the console.
*
* @author Guillaume Lajarige
*/
abstract class PortalBrick extends AbstractBrick
{
const DEFAULT_WIDTH = 1;
const DEFAULT_HEIGHT = 1;
const DEFAULT_MODAL = false;
const DEFAULT_VISIBLE_HOME = true;
const DEFAULT_VISIBLE_NAVIGATION_MENU = true;
const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/tile.html.twig';
static $sRouteName = null;
protected $iWidth;
protected $iHeight;
protected $bModal;
protected $bVisibleHome;
protected $bVisibleNavigationMenu;
protected $sTileTemplatePath;
static function GetRouteName()
{
return static::$sRouteName;
}
/**
* Default attributes values of AbstractBrick are specified in the definition, not the constructor.
*/
function __construct()
{
parent::__construct();
$this->iWidth = static::DEFAULT_WIDTH;
$this->iHeight = static::DEFAULT_HEIGHT;
$this->bModal = static::DEFAULT_MODAL;
$this->bVisibleHome = static::DEFAULT_VISIBLE_HOME;
$this->bVisibleNavigationMenu = static::DEFAULT_VISIBLE_NAVIGATION_MENU;
$this->sTileTemplatePath = static::DEFAULT_TILE_TEMPLATE_PATH;
}
/**
* Returns width of the brick
*
* @return int
*/
public function GetWidth()
{
return $this->iWidth;
}
/**
* Returns height of the brick
*
* @return int
*/
public function GetHeight()
{
return $this->iHeight;
}
/**
* Returns if the brick will show in a modal dialog or not
*
* @return boolean
*/
public function GetModal()
{
return $this->bModal;
}
/**
* Returns if the brick is visible on the portal's home page
*
* @return int
*/
public function GetVisibleHome()
{
return $this->bVisibleHome;
}
/**
* Returns if the brick is visible on the portal's navigation menu
*
* @return int
*/
public function GetVisibleNavigationMenu()
{
return $this->bVisibleNavigationMenu;
}
/**
* Returns the brick tile template path
*
* @return string
*/
public function GetTileTemplatePath()
{
return $this->sTileTemplatePath;
}
/**
* Sets the width of the brick
*
* @param boolean $iWidth
*/
public function SetWidth($iWidth)
{
$this->iWidth = $iWidth;
return $this;
}
/**
* Sets the width of the brick
*
* @param boolean $iWidth
*/
public function SetHeight($iHeight)
{
$this->iHeight = $iHeight;
return $this;
}
/**
* Sets if the brick will show in a modal dialog or not
*
* @param boolean $bModal
*/
public function SetModal($bModal)
{
$this->bModal = $bModal;
return $this;
}
/**
* Sets if the brick is visible on the portal's home
*
* @param boolean $iWidth
*/
public function SetVisibleHome($bVisibleHome)
{
$this->bVisibleHome = $bVisibleHome;
return $this;
}
/**
* Sets if the brick is visible on the portal's navigation menu
*
* @param boolean $iWidth
*/
public function SetVisibleNavigationMenu($bVisibleNavigationMenu)
{
$this->bVisibleNavigationMenu = $bVisibleNavigationMenu;
return $this;
}
/**
* Sets the brick tile template path
*
* @param boolean $sTileTemplatePath
*/
public function SetTileTemplatePath($sTileTemplatePath)
{
$this->sTileTemplatePath = $sTileTemplatePath;
return $this;
}
/**
* Load the brick's data from the xml passed as a ModuleDesignElement.
* This is used to set all the brick attributes at once.
*
* @param \Combodo\iTop\DesignElement $oMDElement
* @return PortalBrick
*/
public function LoadFromXml(DesignElement $oMDElement)
{
parent::LoadFromXml($oMDElement);
// Checking specific elements
foreach ($oMDElement->GetNodes('./*') as $oBrickSubNode)
{
switch ($oBrickSubNode->nodeName)
{
case 'width':
$this->SetWidth((int) $oBrickSubNode->GetText(static::DEFAULT_WIDTH));
break;
case 'height':
$this->SetHeight((int) $oBrickSubNode->GetText(static::DEFAULT_HEIGHT));
break;
case 'modal':
$bModal = ($oBrickSubNode->GetText(static::DEFAULT_MODAL) === 'true');
$this->SetModal($bModal);
break;
case 'visible_home':
$this->SetVisibleHome(($oBrickSubNode->GetText() === 'false') ? false : true );
break;
case 'visible_navigation_menu':
$this->SetVisibleNavigationMenu(($oBrickSubNode->GetText() === 'false') ? false : true );
break;
case 'templates':
$oTemplateNodeList = $oBrickSubNode->GetNodes('template[@id=' . ModuleDesign::XPathQuote('tile') . ']');
if ($oTemplateNodeList->length > 0)
{
$this->SetTileTemplatePath($oTemplateNodeList->item(0)->GetText(static::DEFAULT_TILE_TEMPLATE_PATH));
}
break;
}
}
return $this;
}
}
?>

View File

@@ -0,0 +1,39 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Brick;
use \Combodo\iTop\Portal\Brick\PortalBrick;
/**
* Description of UserProfileBrick
*
* @author Guillaume Lajarige
*/
class UserProfileBrick extends PortalBrick
{
const DEFAULT_PAGE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/user-profile/layout.html.twig';
const DEFAULT_TILE_TEMPLATE_PATH = 'itop-portal-base/portal/src/views/bricks/user-profile/tile.html.twig';
const DEFAULT_VISIBLE_NAVIGATION_MENU = false;
const DEFAULT_VISIBLE_HOME = false;
const DEFAUT_TITLE = 'Brick:Portal:UserProfile:Title';
static $sRouteName = 'p_user_profile_brick';
}
?>

View File

@@ -0,0 +1,881 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Form;
use \Exception;
use \DOMFormatException;
use \Silex\Application;
use \utils;
use \Dict;
use \MetaModel;
use \CMDBSource;
use \DBObject;
use \DBObjectSet;
use \DBObjectSearch;
use \DBObjectSetComparator;
use \InlineImage;
use \UserRights;
use \AttributeDateTime;
use \Combodo\iTop\Form\FormManager;
use \Combodo\iTop\Form\Form;
use \Combodo\iTop\Form\Field\FileUploadField;
use \Combodo\iTop\Form\Field\HiddenField;
use \Combodo\iTop\Form\Field\LabelField;
use \Combodo\iTop\Form\Field\StringField;
use \Combodo\iTop\Form\Field\TextAreaField;
use \Combodo\iTop\Form\Field\SelectField;
use \Combodo\iTop\Form\Field\RadioField;
use \Combodo\iTop\Form\Field\CheckboxField;
use \Combodo\iTop\Form\Validator\IntegerValidator;
use \Combodo\iTop\Form\Validator\NotEmptyValidator;
use \Combodo\iTop\Renderer\Bootstrap\BsFormRenderer;
/**
* Description of objectformmanager
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class ObjectFormManager extends FormManager
{
const ENUM_MODE_VIEW = 'view';
const ENUM_MODE_EDIT = 'edit';
const ENUM_MODE_CREATE = 'create';
protected $oApp;
protected $oObject;
protected $sMode;
protected $sActionRulesToken;
protected $aFormProperties;
protected $aCallbackUrls = array();
/**
* Creates an instance of \Combodo\iTop\Portal\Form\ObjectFormManager from JSON data that must contain at least :
* - formobject_class : The class of the object that is being edited/viewed
* - formmode : view|edit|create
* - values for parent
*
* @param string $sJson
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
static function FromJSON($sJson)
{
$aJson = json_decode($sJson, true);
$oFormManager = parent::FromJSON($sJson);
// Retrieving object to edit
if (!isset($aJson['formobject_class']))
{
throw new Exception('Object class must be defined in order to generate the form');
}
$sObjectClass = $aJson['formobject_class'];
if (!isset($aJson['formobject_id']))
{
$oObject = new $sObjectClass();
}
else
{
$oObject = MetaModel::GetObject($sObjectClass, $aJson['formobject_id'], true);
}
$oFormManager->SetObject($oObject);
// Retrieving form mode
if (!isset($aJson['formmode']))
{
throw new Exception('Form mode must be defined in order to generate the form');
}
$oFormManager->SetMode($aJson['formmode']);
// Retrieving actions rules
if (isset($aJson['formactionrulestoken']))
{
$oFormManager->SetActionRulesToken($aJson['formactionrulestoken']);
}
// Retrieving callback urls
if (!isset($aJson['formcallbacks']))
{
// TODO
}
return $oFormManager;
}
/**
*
* @return \Silex\Application
*/
public function GetApplication()
{
return $this->oApp;
}
/**
*
* @param \Silex\Application $oApp
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
public function SetApplication(Application $oApp)
{
$this->oApp = $oApp;
return $this;
}
/**
*
* @return \DBObject
*/
public function GetObject()
{
return $this->oObject;
}
/**
*
* @param \DBObject $oObject
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
public function SetObject(DBObject $oObject)
{
$this->oObject = $oObject;
return $this;
}
/**
*
* @return string
*/
public function GetMode()
{
return $this->sMode;
}
/**
*
* @param string $sMode
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
public function SetMode($sMode)
{
$this->sMode = $sMode;
return $this;
}
/**
*
* @return string
*/
public function GetActionRulesToken()
{
return $this->sActionRulesToken;
}
/**
*
* @param string $sActionRulesToken
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
public function SetActionRulesToken($sActionRulesToken)
{
$this->sActionRulesToken = $sActionRulesToken;
return $this;
}
/**
*
* @return array
*/
public function GetFormProperties()
{
return $this->aFormProperties;
}
/**
*
* @param array $aFormProperties
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
public function SetFormProperties($aFormProperties)
{
$this->aFormProperties = $aFormProperties;
return $this;
}
/**
*
* @return array
*/
public function GetCallbackUrls()
{
return $this->aCallbackUrls;
}
/**
*
* @param array $aCallbackUrls
* @return \Combodo\iTop\Portal\Form\ObjectFormManager
*/
public function SetCallbackUrls($aCallbackUrls)
{
$this->aCallbackUrls = $aCallbackUrls;
return $this;
}
/**
* Creates a JSON string from the current object including :
* - formobject_class
* - formobject_id
* - formmode
* - values for parent
*
* @return string
*/
public function ToJSON()
{
$aJson = parent::ToJSON();
$aJson['formobject_class'] = get_class($this->oObject);
if ($this->oObject->GetKey() > 0)
$aJson['formobject_id'] = $this->oObject->GetKey();
$aJson['formmode'] = $this->sMode;
$aJson['formactionrulestoken'] = $this->sActionRulesToken;
return $aJson;
}
public function Build()
{
$sObjectClass = get_class($this->oObject);
$aFieldsAtts = array();
$aMandatoryAtts = array();
$aReadonlyAtts = array();
$aHiddenAtts = array();
if ($this->oForm !== null)
{
$oForm = $this->oForm;
}
else
{
$aFormId = 'objectform-' . ((isset($this->aFormProperties['id'])) ? $this->aFormProperties['id'] : 'default') . '-' . uniqid();
$oForm = new Form($aFormId);
$oForm->SetTransactionId(utils::GetNewTransactionId());
}
// Building form from its properties
// - The fields
switch ($this->aFormProperties['type'])
{
case 'custom_list':
case 'static':
foreach ($this->aFormProperties['fields'] as $sAttCode => $aOptions)
{
$iFieldFlags = OPT_ATT_NORMAL;
// Checking if field should be slave
if (isset($aOptions['slave']) && ($aOptions['slave'] === true))
{
$iFieldFlags = $iFieldFlags | OPT_ATT_SLAVE;
}
// Checking if field should be must prompt
if (isset($aOptions['must_prompt']) && ($aOptions['must_prompt'] === true))
{
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTPROMPT;
}
// Checking if field should be must_change
if (isset($aOptions['must_change']) && ($aOptions['must_change'] === true))
{
$iFieldFlags = $iFieldFlags | OPT_ATT_MUSTCHANGE;
}
// Checking if field should be hidden
if (isset($aOptions['hidden']) && ($aOptions['hidden'] === true))
{
$iFieldFlags = $iFieldFlags | OPT_ATT_HIDDEN;
}
// Checking if field should be mandatory
if (isset($aOptions['mandatory']) && ($aOptions['mandatory'] === true))
{
$iFieldFlags = $iFieldFlags | OPT_ATT_MANDATORY;
}
// Checking if field should be readonly
if (isset($aOptions['read_only']) && ($aOptions['read_only'] === true))
{
$iFieldFlags = $iFieldFlags | OPT_ATT_READONLY;
}
// Finally, adding the attribute and its flags
$aFieldsAtts[$sAttCode] = $iFieldFlags;
}
break;
case 'zlist':
foreach (MetaModel::FlattenZList(MetaModel::GetZListItems($sObjectClass, $this->aFormProperties['fields'])) as $sAttCode)
{
$aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
}
break;
}
// - The layout
if ($this->aFormProperties['layout'] !== null)
{
// Checking if we need to render the template from twig to html in order to parse the fields
if ($this->aFormProperties['layout']['type'] === 'twig')
{
// Creating sandbox twig env. to load and test the custom form template
$oTwig = new \Twig_Environment(new \Twig_Loader_String());
$sRendered = $oTwig->render($this->aFormProperties['layout']['content'], array('oRenderer' => $this->oRenderer, 'oObject' => $this->oObject));
}
else
{
$sRendered = $this->aFormProperties['layout']['content'];
}
// Parsing rendered template to find the fields
$oHtmlDocument = new \DOMDocument();
$oHtmlDocument->loadHTML('<root>' . $sRendered . '</root>');
// Adding fields to the list
$oXPath = new \DOMXPath($oHtmlDocument);
foreach ($oXPath->query('//div[@class="form_field"][@data-field-id]') as $oFieldNode)
{
$sFieldId = $oFieldNode->getAttribute('data-field-id');
$sFieldFlags = $oFieldNode->getAttribute('data-field-flags');
$iFieldFlags = OPT_ATT_NORMAL;
// Checking if field has form_path, if not, we add it
if (!$oFieldNode->hasAttribute('data-form-path'))
{
$oFieldNode->setAttribute('data-form-path', $oForm->GetId());
}
// Settings field flags from the data-field-flags attribute
foreach (explode(' ', $sFieldFlags) as $sFieldFlag)
{
if ($sFieldFlag !== '')
{
$sConst = 'OPT_ATT_' . strtoupper(str_replace('_', '', $sFieldFlag));
if (defined($sConst))
{
$iFieldFlags = $iFieldFlags | constant($sConst);
}
else
{
throw new Exception('Flag "' . $sFieldFlag . '" is not valid for field [@data-field-id="' . $sFieldId . '"] in form[@id="' . $this->aFormProperties['id'] . '"]');
}
}
}
// Finally adding field to the list
if (!array_key_exists($sFieldId, $aFieldsAtts))
{
$aFieldsAtts[$sFieldId] = OPT_ATT_NORMAL;
}
$aFieldsAtts[$sFieldId] = $aFieldsAtts[$sFieldId] | $iFieldFlags;
}
// Adding rendered template to the form renderer as the base layout
$this->oRenderer->SetBaseLayout($oHtmlDocument->saveHTML());
}
// Merging flags from metamodel with those from the form
// Also, retrieving mandatory attributes from metamodel to be able to complete the form with them if necessary
if ($this->aFormProperties['type'] !== 'static')
{
foreach (MetaModel::ListAttributeDefs($sObjectClass) as $sAttCode => $oAttDef)
{
// Retrieving object flags
if ($this->oObject->IsNew())
{
$iFieldFlags = $this->oObject->GetInitialStateAttributeFlags($sAttCode);
}
else
{
$iFieldFlags = $this->oObject->GetAttributeFlags($sAttCode);
}
// Merging flags with those from the form definition
// - only if the field if it's in fields list
if (array_key_exists($sAttCode, $aFieldsAtts))
{
$aFieldsAtts[$sAttCode] = $aFieldsAtts[$sAttCode] | $iFieldFlags;
}
// - or it is mandatory and has no value
if ((($iFieldFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY) && ($this->oObject->Get($sAttCode) === ''))
{
if (!array_key_exists($sAttCode, $aFieldsAtts))
{
$aFieldsAtts[$sAttCode] = OPT_ATT_NORMAL;
}
$aFieldsAtts[$sAttCode] = $aFieldsAtts[$sAttCode] | OPT_ATT_MANDATORY;
}
}
}
// Building the form
foreach ($aFieldsAtts as $sAttCode => $iFieldFlags)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sAttCode);
// TODO : Make AttributeDefinition::MakeFormField() for all kind of fields
if (in_array(get_class($oAttDef), array('AttributeString', 'AttributeText', 'AttributeLongText', 'AttributeCaseLog', 'AttributeHTML', 'AttributeFriendlyName', 'AttributeEnum', 'AttributeExternalKey', 'AttributeCustomFields', 'AttributeLinkedSet', 'AttributeLinkedSetIndirect', 'AttributeDate', 'AttributeDateTime')))
{
$oField = $oAttDef->MakeFormField($this->oObject);
if ($this->sMode !== static::ENUM_MODE_VIEW)
{
// Field dependencies
$aFieldDependencies = $oAttDef->GetPrerequisiteAttributes();
if (!empty($aFieldDependencies))
{
$oForm->AddFieldDependencies($oField->GetId(), $aFieldDependencies);
}
// Setting the field flags
// - If it's locked because slave, we force it as read only
if (($iFieldFlags & OPT_ATT_SLAVE) === OPT_ATT_SLAVE)
{
$oField->SetReadOnly(true);
}
// - Else if it's mandatory and has no value, we force it as mandatory
elseif ((($iFieldFlags & OPT_ATT_MANDATORY) === OPT_ATT_MANDATORY) && $oAttDef->IsNull($this->oObject->Get($sAttCode)))
{
$oField->SetMandatory(true);
}
// - Else if it wasn't mandatory or already had a value, and it's hidden, we force it as hidden
elseif (($iFieldFlags & OPT_ATT_HIDDEN) === OPT_ATT_HIDDEN)
{
$oField->SetHidden(true);
}
elseif (($iFieldFlags & OPT_ATT_READONLY) === OPT_ATT_READONLY)
{
$oField->SetReadOnly(true);
}
else
{
// Normal field
}
// Specific operation on field
// - Field that require a transaction id
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\TextAreaField', 'Combodo\\iTop\\Form\\Field\\CaseLogField')))
{
$oField->SetTransactionId($oForm->GetTransactionId());
}
// - Field that require a search endpoint
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\SelectObjectField', 'Combodo\\iTop\\Form\\Field\\LinkedSetField')))
{
if ($this->oApp !== null)
{
$sSearchEndpoint = $this->oApp['url_generator']->generate('p_object_search_generic', array(
'sTargetAttCode' => $oAttDef->GetCode(),
'sHostObjectClass' => get_class($this->oObject),
'sHostObjectId' => ($this->oObject->IsNew()) ? null : $this->oObject->GetKey()
));
$oField->SetSearchEndpoint($sSearchEndpoint);
}
}
// - Field that require an information endpoint
if (in_array(get_class($oField), array('Combodo\\iTop\\Form\\Field\\LinkedSetField')))
{
if ($this->oApp !== null)
{
$oField->SetInformationEndpoint($this->oApp['url_generator']->generate('p_object_get_informations_json'));
}
}
}
else
{
if (($iFieldFlags & OPT_ATT_HIDDEN) === OPT_ATT_HIDDEN)
{
$oField->SetHidden(true);
}
else
{
$oField->SetReadOnly(true);
}
}
$oForm->AddField($oField);
}
else
{
$oField = new LabelField($sAttCode);
$oField->SetReadOnly(true)
->SetHidden(false)
->SetCurrentValue(get_class($oAttDef) . ' : Sorry, that AttributeType is not implemented yet.')
->SetLabel($oAttDef->GetLabel());
$oForm->AddField($oField);
}
}
// Checking dependencies to ensure that all needed fields are in the form
// (This is kind of a garbage collector for dependancies)
foreach ($oForm->GetDependencies() as $sImpactedFieldId => $aDependancies)
{
foreach ($aDependancies as $sDependancyFieldId)
{
if (!$oForm->HasField($sDependancyFieldId))
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sDependancyFieldId);
$oField = $oAttDef->MakeFormField($this->oObject);
$oField->SetHidden(true);
$oForm->AddField($oField);
}
}
}
// Checking if the instance has attachments
if (class_exists('Attachment'))
{
// Checking if the object is allowed for attchments
$bClassAllowed = false;
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach ($aAllowedClasses as $sAllowedClass)
{
if ($this->oObject instanceof $sAllowedClass)
{
$bClassAllowed = true;
break;
}
}
// Adding attachment field
if ($bClassAllowed)
{
$oField = new FileUploadField('attachments_for_form_' . $oForm->GetId());
$oField->SetLabel(Dict::S('Portal:Attachments'))
->SetUploadEndpoint($this->oApp['url_generator']->generate('p_object_attachment_add'))
->SetDownloadEndpoint($this->oApp['url_generator']->generate('p_object_attachment_download', array('sAttachmentId' => '-sAttachmentId-')))
->SetTransactionId($oForm->GetTransactionId())
->SetAllowDelete($this->oApp['combodo.portal.instance.conf']['properties']['attachments']['allow_delete'])
->SetObject($this->oObject);
$oForm->AddField($oField);
}
}
$oForm->Finalize();
$this->oForm = $oForm;
$this->oRenderer->SetForm($this->oForm);
}
/**
* Calls all form fields OnCancel method in order to delegate them the cleanup;
*
* @param array $aArgs
*/
public function OnCancel($aArgs = null)
{
// Ask to each field to clean itself
foreach ($this->oForm->GetFields() as $oField)
{
$oField->OnCancel();
}
// Then clean inlineimages from rich text editor such as TextareaField
// Note : This could be done by TextareaField::OnCancel(), but we consider that could have been done in this form outside the field.
// Also, it would require the field to know the transaction id which it doesn't as of today.
InlineImage::OnFormCancel(utils::GetUploadTempId($this->oForm->GetTransactionId()));
// Then clean attachments
// TODO : This has to be refactored when the function from itop-attachent has been migrated into the core
$this->CancelAttachments();
}
/**
* Validates the form and returns an array with the validation status and the messages.
* If the form is valid, creates/updates the object.
*
* eg :
* array(
* 'status' => true|false
* 'messages' => array(
* 'errors' => array()
* )
*
* @param array $aArgs
* @return array
*/
public function OnSubmit($aArgs = null)
{
$aData = array(
'valid' => true,
'messages' => array(
'success' => array(),
'warnings' => array(), // Not used as of today, just to show that the structure is ready for change like this.
'error' => array()
)
);
// Update object and form
$this->OnUpdate($aArgs);
// Check if form valid
if ($this->oForm->Validate())
{
// The try catch is essentially to start a MySQL transaction in order to ensure that all or none objects are persisted when creating an object with links
try
{
// Starting transaction
CMDBSource::Query('START TRANSACTION');
// Writing object to DB
$bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified());
$this->oObject->DBWrite();
// Finalizing images link to object, otherwise it will be cleaned by the GC
InlineImage::FinalizeInlineImages($this->oObject);
// Finalizing attachments link to object
// TODO : This has to be refactored when the function from itop-attachent has been migrated into the core
if (isset($aArgs['attachmentIds']))
{
$this->FinalizeAttachments($aArgs['attachmentIds']);
}
// Checking if we have to apply a stimulus
if (isset($aArgs['applyStimulus']))
{
$this->oObject->ApplyStimulus($aArgs['applyStimulus']['code']);
}
// Activating triggers only on update
if ($bActivateTriggers)
{
$sTriggersQuery = $this->oApp['combodo.portal.instance.conf']['properties']['triggers_query'];
if ($sTriggersQuery !== null)
{
$aParentClasses = MetaModel::EnumParentClasses(get_class($this->oObject), ENUM_PARENT_CLASSES_ALL);
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL($sTriggersQuery), array(), array('parent_classes' => $aParentClasses));
while ($oTrigger = $oTriggerSet->Fetch())
{
$oTrigger->DoActivate($this->oObject->ToArgs('this'));
}
}
}
// Removing transaction id from DB
// TODO : utils::RemoveTransaction($this->oForm->GetTransactionId()); ?
// Ending transaction with a commit as everything was fine
CMDBSource::Query('COMMIT');
$aData['messages']['success'] += array('_main' => array(Dict::S('Brick:Portal:Object:Form:Message:Saved')));
}
catch (Exception $e)
{
// End transaction with a rollback as something failed
CMDBSource::Query('ROLLBACK');
$aData['valid'] = false;
$aData['messages']['error'] += array('_main' => array($e->getMessage()));
}
}
else
{
// Handle errors
$aData['valid'] = false;
$aData['messages']['error'] += $this->oForm->GetErrorMessages();
}
return $aData;
}
/**
* Updates the form and its fields with the current values
*
* Note : Doesn't update the object, see ObjectFormManager::OnSubmit() for that;
*
* @param array $aArgs
*/
public function OnUpdate($aArgs = null)
{
$aFormProperties = array();
if (is_array($aArgs))
{
// First we need to update the Object with its new values in order to enable the dependents fields to update
if (isset($aArgs['currentValues']))
{
$aCurrentValues = $aArgs['currentValues'];
$sObjectClass = get_class($this->oObject);
foreach ($aCurrentValues as $sAttCode => $value)
{
if (MetaModel::IsValidAttCode($sObjectClass, $sAttCode))
{
$oAttDef = MetaModel::GetAttributeDef($sObjectClass, $sAttCode);
if ($oAttDef->IsLinkSet())
{
// Parsing JSON value
//
// Note : The value was passed as a string instead of an array because the attribute would not be included in the $aCurrentValues when empty.
// Which was an issue when deleting all objects from linkedset
$value = json_decode($value, true);
// Creating set from objects of the form
$sTargetClass = $oAttDef->GetLinkedClass();
$oValueSet = DBObjectSet::FromScratch($sTargetClass);
foreach ($value as $aValue)
{
$iTargetId = (int) $aValue['id'];
// LinkedSet
if (!$oAttDef->IsIndirect())
{
$oLinkedObject = MetaModel::GetObject($sTargetClass, abs($iTargetId));
$oValueSet->AddObject($oLinkedObject);
}
// LinkedSetIndirect
else
{
// New relation
if ($iTargetId < 0)
{
$oLink = MetaModel::NewObject($sTargetClass);
$oLink->Set($oAttDef->GetExtKeyToRemote(), -1 * $iTargetId);
$oLink->Set($oAttDef->GetExtKeyToMe(), $this->oObject->GetKey());
}
// Existing relation
else
{
$oLink = MetaModel::GetObject($sTargetClass, $iTargetId);
}
$oValueSet->AddObject($oLink);
}
}
// Comparing set from db to set from form if linkedset is DIRECT in order to identify removed objects
if (!$oAttDef->IsIndirect())
{
// Retrieving remote object's extkey definition in order to nullify it or completely remove the object regarding its mandatory status
$oExtKeyToMeAttDef = MetaModel::GetAttributeDef($sTargetClass, $oAttDef->GetExtKeyToMe());
if ($oExtKeyToMeAttDef->IsNullAllowed())
{
// Comparing sets
$oDBSet = $this->oObject->Get($sAttCode);
$oDBSetComparator = new DBObjectSetComparator($oDBSet, $oValueSet);
$aDBSetDifferences = $oDBSetComparator->GetDifferences();
// Nullifying remote object's ext key
foreach ($aDBSetDifferences['removed'] as $oRemovedLinkedObject)
{
$oRemovedLinkedObject->Set($oExtKeyToMeAttDef->GetCode(), $oExtKeyToMeAttDef->GetNullValue());
$oValueSet->AddObject($oRemovedLinkedObject);
}
}
}
// Setting value in the object
$this->oObject->Set($sAttCode, $oValueSet);
}
else if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
{
if ($value != null)
{
$value = $oAttDef->GetFormat()->Parse($value);
if (is_object($value))
{
$value = $value->format($oAttDef->GetInternalFormat());
}
}
$this->oObject->Set($sAttCode, $value);
}
elseif ($oAttDef->IsScalar() && is_array($value))
{
$this->oObject->Set($sAttCode, current($value));
}
elseif ($oAttDef->GetEditClass() === 'CustomFields')
{
if (isset($value['template_data']) && $value['template_data'] !== '')
{
$this->oObject->Set($sAttCode, $value);
}
}
else
{
$this->oObject->Set($sAttCode, $value);
}
}
}
$this->oObject->DoComputeValues();
}
// Then we retrieve properties of the form to build
if (isset($aArgs['formProperties']))
{
$aFormProperties = $aArgs['formProperties'];
}
}
// Then we build and update form
$this->SetFormProperties($aFormProperties);
$this->Build();
}
/**
* This is a temporary function until the Attachment refactoring is done. It should be remove once it's done.
* It is inspired from itop-attachments/main.attachments.php / UpdateAttachments()
*
* @param array $aAttachmentIds
*/
protected function FinalizeAttachments($aAttachmentIds)
{
$aRemovedAttachmentsIds = (isset($aAttachmentIds['removed_attachments_ids'])) ? $aAttachmentIds['removed_attachments_ids'] : array();
$aActualAttachmentsIds = (isset($aAttachmentIds['actual_attachments_ids'])) ? $aAttachmentIds['actual_attachments_ids'] : array();
// Removing attachments from currents
if (!empty($aRemovedAttachmentsIds))
{
$oSearch = DBObjectSearch::FromOQL("SELECT Attachment WHERE item_class = :class AND item_id = :item_id");
$oSet = new DBObjectSet($oSearch, array(), array('class' => get_class($this->oObject), 'item_id' => $this->oObject->GetKey()));
while ($oAttachment = $oSet->Fetch())
{
// Remove attachments that are no longer attached to the current object
if (in_array($oAttachment->GetKey(), $aRemovedAttachmentsIds))
{
$oAttachment->DBDelete();
}
}
}
// Processing temporary attachments
$sTempId = session_id() . '_' . $this->oForm->GetTransactionId();
$sOQL = 'SELECT Attachment WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
while ($oAttachment = $oSet->Fetch())
{
// Temp attachment removed
if (in_array($oAttachment->GetKey(), $aRemovedAttachmentsIds))
{
$oAttachment->DBDelete();
}
else
{
$oAttachment->SetItem($this->oObject);
$oAttachment->Set('temp_id', '');
$oAttachment->DBUpdate();
}
}
}
/**
* This is a temporary function until the Attachment refactoring is done. It should be remove once it's done.
* It is inspired from itop-attachments/main.attachments.php / UpdateAttachments()
*/
protected function CancelAttachments()
{
// Processing temporary attachments
$sTempId = session_id() . '_' . $this->oForm->GetTransactionId();
$sOQL = 'SELECT Attachment WHERE temp_id = :temp_id';
$oSearch = DBObjectSearch::FromOQL($sOQL);
$oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId));
while ($oAttachment = $oSet->Fetch())
{
$oAttachment->DBDelete();
}
}
}

View File

@@ -0,0 +1,805 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Helper;
use \Exception;
use \Silex\Application;
use \Symfony\Component\Debug\ErrorHandler;
use \Symfony\Component\Debug\ExceptionHandler;
use \Symfony\Component\HttpFoundation\Request;
use \Twig_SimpleFilter;
use \Dict;
use \utils;
use \UserRights;
use \DOMFormatException;
use \ModuleDesign;
use \MetaModel;
use \DBObjectSearch;
use \DBObjectSet;
use \Combodo\iTop\Portal\Brick\AbstractBrick;
/**
* Contains static methods to help loading / registering classes of the application.
* Mostly used for Controllers / Routers / Entities initialization.
*
* @author Guillaume Lajarige
*/
class ApplicationHelper
{
/**
* Loads classes from the base portal
*
* @param string $sScannedDir Directory to load the files from
* @param string $sFilePattern Pattern of files to load
* @param string $sType Type of files to load, used only in the Exception message, can be anything
* @throws \Exception
*/
static function LoadClasses($sScannedDir, $sFilePattern, $sType)
{
// Loading classes from base portal
foreach (scandir($sScannedDir) as $sFile)
{
if (strpos($sFile, $sFilePattern) !== false && file_exists($sFilepath = $sScannedDir . '/' . $sFile))
{
try
{
require_once $sFilepath;
}
catch (Exception $e)
{
throw new Exception('Error while trying to load ' . $sType . ' ' . $sFile);
}
}
}
}
/**
* Loads controllers from the base portal
*
* @param string $sScannedDir Directory to load the controllers from
* @throws \Exception
*/
static function LoadControllers($sScannedDir = null)
{
if ($sScannedDir === null)
{
$sScannedDir = __DIR__ . '/../controllers';
}
// Loading controllers from base portal (those from modules have already been loaded by module.xxx.php files)
self::LoadClasses($sScannedDir, 'controller.class.inc.php', 'controller');
}
/**
* Loads routers from the base portal
*
* @param string $sScannedDir Directory to load the routers from
* @throws \Exception
*/
static function LoadRouters($sScannedDir = null)
{
if ($sScannedDir === null)
{
$sScannedDir = __DIR__ . '/../routers';
}
// Loading routers from base portal (those from modules have already been loaded by module.xxx.php files)
self::LoadClasses($sScannedDir, 'router.class.inc.php', 'router');
}
/**
* Loads bricks from the base portal
*
* @param string $sScannedDir Directory to load the bricks from
* @throws \Exception
*/
static function LoadBricks($sScannedDir = null)
{
if ($sScannedDir === null)
{
$sScannedDir = __DIR__ . '/../entities';
}
// Loading bricks from base portal (those from modules have already been loaded by module.xxx.php files)
self::LoadClasses($sScannedDir, 'brick.class.inc.php', 'brick');
}
/**
* Registers routes in the Silex Application from all declared Router classes
*
* @param \Silex\Application $oApp
* @throws \Exception
*/
static function RegisterRoutes(Application $oApp)
{
$aAllRoutes = array();
foreach (get_declared_classes() as $sPHPClass)
{
if (is_subclass_of($sPHPClass, 'Combodo\\iTop\\Portal\\Router\\AbstractRouter'))
{
try
{
// Registering to Silex Application
$sPHPClass::RegisterAllRoutes($oApp);
// Registering them together so we can access them from everywhere
foreach ($sPHPClass::GetRoutes() as $aRoute)
{
$aAllRoutes[$aRoute['bind']] = $aRoute;
}
}
catch (Exception $e)
{
throw new Exception('Error while trying to register routes');
}
}
}
$oApp['combodo.portal.instance.routes'] = $aAllRoutes;
}
/**
* Returns all registered routes for the current portal instance
*
* @param \Silex\Application $oApp
* @param boolean $bNamesOnly If set to true, function will return only the routes' names, not the objects
* @return array
*/
static function GetRoutes(Application $oApp, $bNamesOnly = false)
{
return ($bNamesOnly) ? array_keys($oApp['combodo.portal.instance.routes']) : $oApp['combodo.portal.instance.routes'];
}
/**
* Registers Twig extensions such as filters or functions.
* It allows us to access some stuff directly in twig.
*
* @param \Silex\Application $oApp
*/
static function RegisterTwigExtensions(Application $oApp)
{
// A filter to translate a string via the Dict::S function
// Usage in twig : {{ 'String:ToTranslate'|dict_s }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('dict_s', function($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
{
return Dict::S($sStringCode, $sDefault, $bUserLanguageOnly);
})
);
// A filter to format a string via the Dict::Format function
// Usage in twig : {{ 'String:ToTranslate'|dict_format() }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('dict_format', function($sStringCode, $sParam01 = null, $sParam02 = null, $sParam03 = null, $sParam04 = null)
{
return Dict::Format($sStringCode, $sParam01, $sParam02, $sParam03, $sParam04);
})
);
// Filters to enable base64 encode/decode
// Usage in twig : {{ 'String to encode'|base64_encode }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('base64_encode', 'base64_encode'));
$oApp['twig']->addFilter(new Twig_SimpleFilter('base64_decode', 'base64_decode'));
// Filters to enable json decode (encode already exists)
// Usage in twig : {{ aSomeArray|json_decode }}
$oApp['twig']->addFilter(new Twig_SimpleFilter('json_decode', function($sJsonString, $bAssoc = false)
{
return json_decode($sJsonString, $bAssoc);
})
);
}
/**
* Registers an exception handler that will intercept controllers exceptions and display them in a nice template.
* Note : It is only active when $oApp['debug'] is false
*
* @param Application $oApp
*/
static function RegisterExceptionHandler(Application $oApp)
{
ErrorHandler::register();
ExceptionHandler::register(($oApp['debug'] === true));
if (!$oApp['debug'])
{
$oApp->error(function(Exception $e, $code) use ($oApp)
{
$aData = array(
'exception' => $e,
'code' => $code,
'error_title' => '',
'error_message' => $e->getMessage()
);
switch ($code)
{
case 404:
$aData['error_title'] = Dict::S('Error:HTTP:404');
break;
default:
$aData['error_title'] = Dict::S('Error:HTTP:500');
break;
}
if ($oApp['request']->isXmlHttpRequest())
{
$oResponse = $oApp->json($aData, $code);
}
else
{
$oResponse = $oApp['twig']->render('itop-portal-base/portal/src/views/errors/layout.html.twig', $aData);
}
return $oResponse;
});
}
}
/**
* Loads the portal instance configuration from its module design into the Silex application
*
* @param \Silex\Application $oApp
* @throws Exception
*/
static function LoadPortalConfiguration(Application $oApp)
{
try
{
// Loading file
if (!defined('PORTAL_ID'))
{
throw new Exception('Cannot load module design, Portal ID is not defined');
}
$oDesign = new ModuleDesign(PORTAL_ID);
// Parsing file
// - Default values
$aPortalConf = array(
'properties' => array(
'id' => PORTAL_ID,
'name' => 'Page:DefaultTitle',
'logo' => null,
'themes' => array(
'bootstrap' => $oApp['combodo.portal.base.absolute_url'] . 'css/bootstrap-theme.min.css',
'portal' => $oApp['combodo.portal.base.absolute_url'] . 'css/portal.css',
'others' => array(),
),
'templates' => array(
'layout' => 'itop-portal-base/portal/src/views/layout.html.twig',
'home' => 'itop-portal-base/portal/src/views/home/layout.html.twig'
),
'triggers_query' => null,
'attachments' => array(
'allow_delete' => true
)
),
'portals' => array(),
'forms' => array(),
'bricks' => array(),
'bricks_total_width' => 0
);
// - Global portal properties
foreach ($oDesign->GetNodes('/module_design/properties/*') as $oPropertyNode)
{
$bPropertyNodeError = false;
switch ($oPropertyNode->nodeName)
{
case 'name':
case 'triggers_query':
$aPortalConf['properties'][$oPropertyNode->nodeName] = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
break;
case 'logo':
$sLogoUri = $oPropertyNode->GetText($aPortalConf['properties'][$oPropertyNode->nodeName]);
if ($sLogoUri === null)
{
// There is no logo : do nothing
}
elseif (preg_match('/^http/', $sLogoUri))
{
// The uri is already complete : do nothing
}
else
{
// We prefix it with the server base url
$sLogoUri = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . $sLogoUri;
}
$aPortalConf['properties'][$oPropertyNode->nodeName] = $sLogoUri;
break;
case 'themes':
case 'templates':
foreach ($oPropertyNode->GetNodes('template|theme') as $oSubNode)
{
if (!$oSubNode->hasAttribute('id') || $oSubNode->GetText(null) === null)
{
throw new DOMFormatException('Tag ' . $oSubNode->nodeName . ' must have a "id" attribute as well as a value', null, null, $oSubNode);
}
$sNodeId = $oSubNode->getAttribute('id');
switch ($oSubNode->nodeName)
{
case 'theme':
switch ($sNodeId)
{
case 'bootstrap':
case 'portal':
case 'custom':
$aPortalConf['properties']['themes'][$sNodeId] = $oApp['combodo.portal.instance.absolute_url'] . '' . $oSubNode->GetText(null);
break;
default:
$aPortalConf['properties']['themes']['others'][] = $oApp['combodo.portal.instance.absolute_url'] . '' . $oSubNode->GetText(null);
break;
}
break;
case 'template':
switch ($sNodeId)
{
case 'layout':
case 'home':
$aPortalConf['properties']['templates'][$sNodeId] = $oSubNode->GetText(null);
break;
default:
throw new DOMFormatException('Value "' . $sNodeId . '" is not handled for template[@id]', null, null, $oSubNode);
break;
}
break;
}
}
break;
case 'attachments':
foreach ($oPropertyNode->GetNodes('*') as $oSubNode)
{
switch ($oSubNode->nodeName)
{
case 'allow_delete':
$sValue = $oSubNode->GetText();
// If the text is null, we keep the default value
// Else we set it
if ($sValue !== null)
{
$aPortalConf['properties']['attachments'][$oSubNode->nodeName] = ($sValue === 'true') ? true : false;
}
break;
}
}
break;
}
}
// - User allowed portals
$aPortalConf['portals'] = UserRights::GetAllowedPortals();
// - Bricks
$aPortalConf = static::LoadBricksConfiguration($oApp, $oDesign) + $aPortalConf;
// - Forms
$aPortalConf['forms'] = static::LoadFormsConfiguration($oApp, $oDesign);
// - Scopes
static::LoadScopesConfiguration($oApp, $oDesign);
// - Action rules
static::LoadActionRulesConfiguration($oApp, $oDesign);
$oApp['combodo.portal.instance.conf'] = $aPortalConf;
}
catch (Exception $e)
{
throw new Exception('Error while parsing portal configuration file : ' . $e->getMessage());
}
}
/**
* Loads the current user and stores it in the Silex application so we can use it wherever in the application
*
* @param \Silex\Application $oApp
* @throws Exception
*/
static function LoadCurrentUser(Application $oApp)
{
$oUser = UserRights::GetUserObject();
if ($oUser === null)
{
throw new Exception('Could not load connected user.');
}
$oApp['combodo.current_user'] = $oUser;
}
/**
* Loads the brick's security from the OQL queries to profiles arrays
*
* @param \Combodo\iTop\Portal\Helper\AbstractBrick $oBrick
*/
static function LoadBrickSecurity(AbstractBrick &$oBrick)
{
try
{
// Allowed profiles
if ($oBrick->GetAllowedProfilesOql() !== null && $oBrick->GetAllowedProfilesOql() !== '')
{
$oSearch = DBObjectSearch::FromOQL($oBrick->GetAllowedProfilesOql());
$oSet = new DBObjectSet($oSearch);
while ($oProfile = $oSet->Fetch())
{
$oBrick->AddAllowedProfile($oProfile->Get('name'));
}
}
// Denied profiles
if ($oBrick->GetDeniedProfilesOql() !== null && $oBrick->GetDeniedProfilesOql() !== '')
{
$oSearch = DBObjectSearch::FromOQL($oBrick->GetDeniedProfilesOql());
$oSet = new DBObjectSet($oSearch);
while ($oProfile = $oSet->Fetch())
{
$oBrick->AddDeniedProfile($oProfile->Get('name'));
}
}
}
catch (Exception $e)
{
throw new Exception('Error while loading security from ' . $oBrick->GetId() . ' brick');
}
}
/**
* Finds an AbstractBrick loaded in the $oApp instance configuration from its ID.
*
* @param \Silex\Application $oApp
* @param string $sBrickId
* @return \Combodo\iTop\Portal\Brick\AbstractBrick
* @throws Exception
*/
static function GetLoadedBrickFromId(Application $oApp, $sBrickId)
{
$bFound = false;
foreach ($oApp['combodo.portal.instance.conf']['bricks'] as $oBrick)
{
if ($oBrick->GetId() === $sBrickId)
{
$bFound = true;
break;
}
}
if (!$bFound)
{
throw new Exception('Brick with id = "' . $sBrickId . '" was not found among loaded bricks.');
}
return $oBrick;
}
/**
* Return the form properties for the $sClassname in $sMode.
*
* If not found, tries to find one from the closest parent class.
* Else returns a default form based on zlist 'details'
*
* @param Application $oApp
* @param string $sClass Object class to find a form for
* @param string $sMode Form mode to find (view|edit|create)
* @return array
*/
static function GetLoadedFormFromClass(Application $oApp, $sClass, $sMode)
{
$aForms = $oApp['combodo.portal.instance.conf']['forms'];
// We try to find the form for that class
if (isset($aForms[$sClass]) && isset($aForms[$sClass][$sMode]))
{
$aForm = $aForms[$sClass][$sMode];
}
// If not found, we try find one from the closest parent class
else
{
$bFound = false;
foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass)
{
if (isset($aForms[$sParentClass]) && isset($aForms[$sParentClass][$sMode]))
{
$aForm = $aForms[$sParentClass][$sMode];
$bFound = true;
break;
}
}
// If we have still not found one, we return a default form
if (!$bFound)
{
$aForm = array(
'id' => 'default',
'type' => 'zlist',
'fields' => 'details',
'layout' => null
);
}
}
return $aForm;
}
/**
* Loads the bricks configuration from the module design XML and returns it as an hash array containing :
* - 'brick' => array of PortalBrick objects
* - 'bricks_total_width' => an integer used to create the home page grid
*
* @param \Silex\Application $oApp
* @param ModuleDesign $oDesign
* @return array
* @throws Exception
* @throws DOMFormatException
*/
static protected function LoadBricksConfiguration(Application $oApp, ModuleDesign $oDesign)
{
$aPortalConf = array(
'bricks' => array(),
'bricks_total_width' => 0,
'bricks_home_count' => 0,
'bricks_navigation_menu_count' => 0
);
foreach ($oDesign->GetNodes('/module_design/bricks/brick') as $oBrickNode)
{
try
{
$sBrickClass = $oBrickNode->getAttribute('xsi:type');
if (class_exists($sBrickClass))
{
$oBrick = new $sBrickClass();
$oBrick->LoadFromXml($oBrickNode);
static::LoadBrickSecurity($oBrick);
// GLA : This didn't work has the modal flag was set for all instances of that brick
// // Checking brick modal flag
// if ($oBrick->GetModal())
// {
// // We have to extract / replace the array as we can modify $oApp values directly
// $aRoutes = $oApp['combodo.portal.instance.routes'];
// // Init brick's array if necessary
// if (!isset($aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']))
// {
// $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr'] = array();
// }
// // Add modal datas for the brick
// $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']['data-toggle'] = 'modal';
// $aRoutes[$oBrick->GetRouteName()]['navigation_menu_attr']['data-target'] = '#modal-for-all';
// // Finally, replace array in $oApp
// $oApp['combodo.portal.instance.routes'] = $aRoutes;
// }
// Checking brick security
if ($oBrick->IsGrantedForProfiles(UserRights::ListProfiles()))
{
$aPortalConf['bricks'][] = $oBrick;
$aPortalConf['bricks_total_width'] += $oBrick->GetWidth();
if ($oBrick->GetVisibleHome())
{
$aPortalConf['bricks_home_count']++;
}
if ($oBrick->GetVisibleNavigationMenu())
{
$aPortalConf['bricks_navigation_menu_count']++;
}
}
}
else
{
throw new DOMFormatException('Unknown brick class "' . $sBrickClass . '" from xsi:type attribute', null, null, $oBrickNode);
}
}
catch (DOMFormatException $e)
{
throw new Exception('Could not create brick (' . $sBrickClass . ') from XML because of a DOM problem : ' . $e->getMessage());
}
catch (Exception $e)
{
throw new Exception('Could not create brick (' . $sBrickClass . ') from XML : ' . $oBrickNode->Dump() . ' ' . $e->getMessage());
}
}
// - Sorting bricks by rank
usort($aPortalConf['bricks'], function($a, $b)
{
return $a->GetRank() > $b->GetRank();
});
return $aPortalConf;
}
/**
* Loads the forms configuration from the module design XML and returns it as an array containing :
* - <CLASSNAME> => array(
* 'view'|'edit'|'create' => array(
* 'fields_type' => 'custom_list'|'twig'|'zlist',
* 'fields' => <CONTENT>
* ),
* ...
* ),
* ...
*
* @param \Silex\Application $oApp
* @param ModuleDesign $oDesign
* @return array
* @throws Exception
* @throws DOMFormatException
*/
static protected function LoadFormsConfiguration(Application $oApp, ModuleDesign $oDesign)
{
$aForms = array();
foreach ($oDesign->GetNodes('/module_design/forms/form') as $oFormNode)
{
try
{
// Parsing form id
if ($oFormNode->getAttribute('id') === '')
{
throw new DOMFormatException('form tag must have an id attribute', null, null, $oFormNode);
}
// Parsing form object class
if ($oFormNode->GetUniqueElement('class')->GetText() !== null)
{
$sFormClass = $oFormNode->GetUniqueElement('class')->GetText();
// Parsing availables modes for that form (view, edit, create)
if (($oFormNode->GetOptionalElement('modes') !== null) && ($oFormNode->GetOptionalElement('modes')->GetNodes('mode')->length > 0))
{
$aModes = array();
foreach ($oFormNode->GetOptionalElement('modes')->GetNodes('mode') as $oModeNode)
{
if ($oModeNode->getAttribute('id') !== '')
{
$aModes[] = $oModeNode->getAttribute('id');
}
else
{
throw new DOMFormatException('Mode tag must have an id attribute', null, null, $oFormNode);
}
}
}
else
{
$aModes = array('view', 'edit', 'create');
}
// Parsing fields
$aFields = array(
'id' => $oFormNode->getAttribute('id'),
'type' => null,
'fields' => null,
'layout' => null
);
// ... either enumerated fields ...
if ($oFormNode->GetOptionalElement('fields') !== null)
{
$aFields['type'] = 'custom_list';
$aFields['fields'] = array();
foreach ($oFormNode->GetOptionalElement('fields')->GetNodes('field') as $oFieldNode)
{
$sFieldId = $oFieldNode->getAttribute('id');
if ($sFieldId !== '')
{
$aField = array();
// Parsing field options like read_only, hidden and mandatory
if ($oFieldNode->GetOptionalElement('read_only'))
{
$aField['readonly'] = ($oFieldNode->GetOptionalElement('read_only')->GetText('true') === 'true') ? true : false;
}
if ($oFieldNode->GetOptionalElement('mandatory'))
{
$aField['mandatory'] = ($oFieldNode->GetOptionalElement('mandatory')->GetText('true') === 'true') ? true : false;
}
if ($oFieldNode->GetOptionalElement('hidden'))
{
$aField['hidden'] = ($oFieldNode->GetOptionalElement('hidden')->GetText('true') === 'true') ? true : false;
}
$aFields['fields'][$sFieldId] = $aField;
}
else
{
throw new DOMFormatException('Field tag must have an id attribute', null, null, $oFormNode);
}
}
}
// // ... or a specified zlist
// elseif ($oFormNode->GetOptionalElement('presentation') !== null)
// {
// // This is not implemented yet as it was rejected until futher notice.
// }
// ... or the default zlist
else
{
$aFields['type'] = 'zlist';
$aFields['fields'] = 'details';
}
// Parsing presentation
if ($oFormNode->GetOptionalElement('twig') !== null)
{
// Extracting the twig template and removing the first and last lines (twig tags)
$sXml = $oDesign->saveXML($oFormNode->GetOptionalElement('twig'));
$sXml = preg_replace('/^.+\n/', '', $sXml);
$sXml = preg_replace('/\n.+$/', '', $sXml);
$aFields['layout'] = array(
'type' => (preg_match('/\{\{|\{\#|\{\%/', $sXml) === 1) ? 'twig' : 'xhtml',
'content' => $sXml
);
}
// Adding form for each class / mode
foreach ($aModes as $sMode)
{
if (!isset($aForms[$sFormClass]))
{
$aForms[$sFormClass] = array();
}
if (!isset($aForms[$sFormClass][$sMode]))
{
$aForms[$sFormClass][$sMode] = $aFields;
}
else
{
throw new DOMFormatException('There is already a form for the class "' . $sFormClass . '" in "' . $sMode . '"', null, null, $oFormNode);
}
}
}
else
{
throw new DOMFormatException('Class tag must be defined', null, null, $oFormNode);
}
}
catch (DOMFormatException $e)
{
throw new Exception('Could not create from [id="' . $oFormNode->getAttribute('id') . '"] from XML because of a DOM problem : ' . $e->getMessage());
}
catch (Exception $e)
{
throw new Exception('Could not create from from XML : ' . $oFormNode->Dump() . ' ' . $e->getMessage());
}
}
return $aForms;
}
/**
* Loads the scopes configuration from the module design XML
*
* @param \Silex\Application $oApp
* @param ModuleDesign $oDesign
*/
static protected function LoadScopesConfiguration(Application $oApp, ModuleDesign $oDesign)
{
$oApp['scope_validator']->Init($oDesign->GetNodes('/module_design/classes/class'));
}
/**
* Loads the context helper from the module design XML
*
* @param \Silex\Application $oApp
* @param ModuleDesign $oDesign
*/
static protected function LoadActionRulesConfiguration(Application $oApp, ModuleDesign $oDesign)
{
$oApp['context_manipulator']->Init($oDesign->GetNodes('/module_design/action_rules/action_rule'));
}
}
?>

View File

@@ -0,0 +1,432 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Helper;
use \Exception;
use \Silex\Application;
use \DOMNodeList;
use \DOMFormatException;
use \DBObject;
use \DBSearch;
use \DBObjectSet;
use \BinaryExpression;
use \FieldExpression;
use \ScalarExpression;
use \iTopObjectCopier;
use \Combodo\iTop\DesignElement;
use \Combodo\iTop\Portal\Brick\Portalbrick;
class ContextManipulatorHelper
{
const ENUM_RULE_CALLBACK_BACK = 'back';
const ENUM_RULE_CALLBACK_GOTO = 'goto';
const ENUM_RULE_CALLBACK_OPEN = 'open';
const ENUM_RULE_CALLBACK_OPEN_VIEW = 'view';
const ENUM_RULE_CALLBACK_OPEN_EDIT = 'edit';
const DEFAULT_RULE_CALLBACK_OPEN = self::ENUM_RULE_CALLBACK_OPEN_VIEW;
protected $aRules;
public function __construct()
{
$this->aRules = array();
}
/**
* Initializes the ScopeValidator by generating and caching the scopes compilation in the $this->sCachePath.$this->sFilename file.
*
* @param DOMNodeList $oNodes
* @throws DOMFormatException
* @throws Exception
*/
public function Init(DOMNodeList $oNodes)
{
$this->aRules = array();
// Iterating over the scope nodes
foreach ($oNodes as $oRuleNode)
{
// Retrieving mandatory id attribute
$sRuleId = $oRuleNode->getAttribute('id');
if ($sRuleId === '')
{
throw new DOMFormatException('Rule tag must have an id attribute.', null, null, $oRuleNode);
}
// Setting if the rule needs a source object
$bNeedsSource = false;
// Note : preset and retrofit are no longer plurals as it should match as much as possible iTopObjectCopier specs. We use plurals only in the xml for the collection tags
$aRule = array(
'source_oql' => null,
'dest_class' => null,
'preset' => array(),
'retrofit' => array(),
'submit' => null,
'cancel' => null
);
// Iterating over the rule's nodes
foreach ($oRuleNode->childNodes as $oSubNode)
{
$sSubNodeName = $oSubNode->nodeName;
switch ($sSubNodeName)
{
case 'source_class':
$aRule['source_oql'] = 'SELECT ' . $oSubNode->GetText();
break;
case 'source_oql':
case 'dest_class':
$aRule[$sSubNodeName] = $oSubNode->GetText();
break;
case 'presets':
case 'retrofits':
foreach ($oSubNode->childNodes as $oActionNode)
{
// Note : Caution, the index of $aRule is now $oActionNode->nodeName instead of $sSubNodeName, as we want to match iTopObjectCopier specs like told previously
if (in_array($oActionNode->nodeName, array('preset', 'retrofit')))
{
$sActionText = $oActionNode->GetText();
$aRule[$oActionNode->nodeName][] = $sActionText;
// Checking if the rule needs a source object
if (substr($sActionText, 0, 4) === 'copy')
{
$bNeedsSource = true;
}
}
}
break;
case 'submit':
case 'cancel':
// Retrieving callback type and checking that it is allowed
$sType = $oSubNode->getAttribute('xsi:type');
if ($sType === '')
{
throw new DOMFormatException($sSubNodeName . ' must have an xsi:type attribute.', null, null, $oSubNode);
}
if (($sType === static::ENUM_RULE_CALLBACK_OPEN) && ($sSubNodeName === 'cancel'))
{
throw new DOMFormatException('Cancel tag cannot be of type ' . $sType . '.', null, null, $oSubNode);
}
$aRule[$sSubNodeName] = array('type' => $sType);
switch ($sType)
{
case static::ENUM_RULE_CALLBACK_BACK:
// Default value
$sRefresh = false;
// Retrieving value
$oRefreshNode = $oSubNode->GetOptionalElement('refresh');
if (($oRefreshNode !== null) && ($oRefreshNode->GetText() !== null))
{
$sRefresh = $oRefreshNode->GetText();
}
$aRule[$sSubNodeName]['refresh'] = $sRefresh;
break;
case static::ENUM_RULE_CALLBACK_GOTO:
// Retrieving value
$sBrickId = $oSubNode->GetUniqueElement('brick')->GetText();
if ($sBrickId === null)
{
throw new DOMFormatException('Brick tag value must not be empty.', null, null, $oSubNode);
}
$aRule[$sSubNodeName]['brick_id'] = $sBrickId;
break;
case static::ENUM_RULE_CALLBACK_OPEN:
// Default value
$sMode = static::ENUM_RULE_CALLBACK_OPEN_VIEW;
// Retrieving value
$oModeNode = $oSubNode->GetOptionalElement('mode');
if (($oModeNode !== null) && ($oModeNode->GetText() !== null))
{
$sMode = $oModeNode->GetText();
}
$aRule[$sSubNodeName]['mode'] = $sMode;
break;
}
break;
}
}
// If there is no source information we check if there is a preset that requires a copy in order to throw an exception
if (($aRule['source_oql'] === null) && ($bNeedsSource === true))
{
throw new DOMFormatException('Rule tag must have either a "source_oql" or a "source_class" child node.', null, null, $oRuleNode);
}
$this->aRules[$sRuleId] = $aRule;
}
}
/**
* Returns a hash array of rules
*
* @return array
*/
public function GetRules()
{
return $this->aRules;
}
/**
* Return the rule identified by its ID, as a hash array
*
* @param string $sId
* @return array
*/
public function GetRule($sId)
{
if (!array_key_exists($sId, $this->aRules))
{
throw new Exception('Context creator : Could not find "' . $sId . '" in the rules list');
}
return $this->aRules[$sId];
}
/**
* Prepare the $oObject passed as a reference with the $aData
*
* $aData must be of the form :
* array(
* 'rules' => array(
* 'rule-id-1',
* 'rule-id-2',
* ...
* ),
* 'sources' => array(
* <DBObject1 class> => <DBObject1 id>,
* <DBObject2 class> => <DBObject2 id>,
* ...
* )
* )
*
* @param array $aData
* @param DBObject $oObject
*/
public function PrepareObject(array $aData, DBObject &$oObject)
{
if (isset($aData['rules']) && isset($aData['sources']))
{
$aRules = $aData['rules'];
$aSources = $aData['sources'];
foreach ($aData['rules'] as $sId)
{
// Retrieveing current rule
$aRule = $this->GetRule($sId);
// Retrieving source object if needed
if ($aRule['source_oql'] !== null)
{
// Preparing query to retrieve source object(s)
$oSearch = DBSearch::FromOQL($aRule['source_oql']);
$sSearchClass = $oSearch->GetClass();
$aSearchParams = $oSearch->GetInternalParams();
if (array_key_exists($sSearchClass, $aSources))
{
$sourceId = $aSources[$sSearchClass];
if (array_key_exists('id', $oSearch->GetQueryParams()))
{
if (is_array($sourceId))
{
throw new Exception('Context creator : ":id" parameter in rule "' . $sId . '" cannot be an array (This is a limitation of DBSearch)');
}
$aSearchParams['id'] = $sourceId;
}
else
{
if (!is_array($sourceId))
{
$sourceId = array($sourceId);
}
$iLoopMax = count($sourceId);
$oFullBinExpr = null;
for ($i = 0; $i < $iLoopMax; $i++)
{
// - Building full search expression
$oBinExpr = new BinaryExpression(new FieldExpression('id', $oSearch->GetClassAlias()), '=', new ScalarExpression($sourceId[$i]));
if ($i === 0)
{
$oFullBinExpr = $oBinExpr;
}
else
{
$oFullBinExpr = new BinaryExpression($oFullBinExpr, 'OR', $oBinExpr);
}
// - Adding it to the query when complete
if ($i === ($iLoopMax - 1))
{
$oSearch->AddConditionExpression($oFullBinExpr);
}
}
}
}
// Retrieving source object(s) and applying rules
$oSet = new DBObjectSet($oSearch, array(), $aSearchParams);
while ($oSourceObject = $oSet->Fetch())
{
// Changing behaviour to remove usage of ObjectCopier as its now being integrated in the core
// Old code : iTopObjectCopier::PrepareObject($aRule, $oObject, $oSourceObject);
// Presets
if (isset($aRule['preset']) && !empty($aRule['preset']))
{
$oObject->ExecActions($aRule['preset'], array('source' => $oSourceObject));
}
// Retrofits
if (isset($aRule['retrofit']) && !empty($aRule['retrofit']))
{
$oSourceObject->ExecActions($aRule['retrofit'], array('source' => $oObject));
}
}
}
else
{
// Changing behaviour to remove usage of ObjectCopier as its now being integrated in the core
// Old code : iTopObjectCopier::PrepareObject($aRule, $oObject, $oObject);
// Presets
if (isset($aRule['preset']) && !empty($aRule['preset']))
{
$oObject->ExecActions($aRule['preset'], array('source' => $oObject));
}
}
}
}
}
/**
* Returns a hash array of urls for each type of callback
*
* eg :
* array(
* 'submit' => 'http://localhost/',
* 'cancel' => null
* );
*
* @param \Silex\Application $oApp
* @param array $aData
* @param \DBObject $oObject
* @param boolean $bModal
* @return array
*/
public function GetCallbackUrls(Application $oApp, array $aData, DBObject $oObject, $bModal = false)
{
$aResults = array(
'submit' => null,
'cancel' => null
);
if (isset($aData['rules']))
{
foreach ($aData['rules'] as $sId)
{
// Retrieveing current rule
$aRule = $this->GetRule($sId);
// For each type of callbacks, we check if there is a rule to apply
foreach (array('submit', 'cancel') as $sCallbackName)
{
if (is_array($aRule[$sCallbackName]))
{
// Previously declared rule on a callback is overwritten by the last
$sCallbackUrl = null;
switch ($aRule[$sCallbackName]['type'])
{
case static::ENUM_RULE_CALLBACK_BACK:
if (!$bModal)
{
$sCallbackUrl = ($_SERVER['HTTP_REFERER'] !== '') ? $_SERVER['HTTP_REFERER'] : null;
}
break;
case static::ENUM_RULE_CALLBACK_GOTO:
$oBrick = ApplicationHelper::GetLoadedBrickFromId($oApp, $aRule[$sCallbackName]['brick_id']);
$sCallbackUrl = $oApp['url_generator']->generate($oBrick->GetRouteName(), array('sBrickId' => $oBrick->GetId()));
break;
case static::ENUM_RULE_CALLBACK_OPEN:
$sCallbackUrl = ($oObject->IsNew()) ? null : $oApp['url_generator']->generate('p_object_' . $aRule[$sCallbackName]['mode'], array('sObjectClass' => get_class($oObject), 'sObjectId' => $oObject->GetKey()));
break;
}
$aResults[$sCallbackName] = $sCallbackUrl;
}
}
}
}
return $aResults;
}
/**
* Encodes a token made out of the rules.
*
* Token = base64_encode( json_encode( array( 'rules' => array(), 'sources' => array() ) ) )
*
* To retrieve it has
*
* @param array $aRules
* @param array $aObjects
* @return string
*/
static public function EncodeRulesToken($aRules, $aObjects = array())
{
// Getting necessary information from objects
$aSources = array();
foreach ($aObjects as $oObject)
{
$aSources[get_class($oObject)] = $oObject->GetKey();
}
// Preparing data
$aTokenRules = array(
'rules' => $aRules,
'sources' => $aSources
);
// Returning tokenised data
return base64_encode(json_encode($aTokenRules));
}
/**
* Decodes a token made out of the rules
*
* @param string $sToken
* @return array
*/
static public function DecodeRulesToken($sToken)
{
return json_decode(base64_decode($sToken), true);
}
}
?>

View File

@@ -0,0 +1,612 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Helper;
use \Exception;
use \DOMNodeList;
use \DOMFormatException;
use \utils;
use \ProfilesConfig;
use \MetaModel;
use \DBSearch;
use \DBUnionSearch;
use \Combodo\iTop\DesignElement;
class LifecycleValidatorHelper
{
const ENUM_TYPE_ALLOW = 'allow';
const ENUM_TYPE_RESTRICT = 'restrict';
const DEFAULT_GENERATED_CLASS = 'PortalLifecycleValues';
protected $sCachePath;
protected $sFilename;
protected $sInstancePrefix;
protected $sGeneratedClass;
protected $aProfilesMatrix;
public static function EnumTypeValues()
{
return array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT);
}
public function __construct($sFilename, $sCachePath = null)
{
$this->sFilename = $sFilename;
$this->sCachePath = $sCachePath;
$this->sInstancePrefix = '';
$this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
$this->aProfilesMatrix = array();
}
/**
* Returns the path where to cache the compiled lifecycles file
*
* @return string
*/
public function GetCachePath()
{
return $this->sCachePath;
}
/**
* Returns the name of the compiled lifecycles file
*
* @return string
*/
public function GetFilename()
{
return $this->sFilename;
}
/**
* Returns the instance prefix used for the generated lifecycles class name
*
* @return string
*/
public function GetInstancePrefix()
{
return $this->sInstancePrefix;
}
/**
* Returns the name of the generated lifecycles class
*
* @return string
*/
public function GetGeneratedClass()
{
return $this->sGeneratedClass;
}
/**
* Sets the lifecycle validator instance prefix.
*
* This is used to create a unique lifecycle values class in the cache directory (/data/cache-<ENV>) as there can be several instance of the portal.
*
* @param string $sInstancePrefix
* @return \Combodo\iTop\Portal\Helper\LifecycleValidatorHelper
*/
public function SetInstancePrefix($sInstancePrefix)
{
$sInstancePrefix = preg_replace('/[-_]/', ' ', $sInstancePrefix);
$sInstancePrefix = ucwords($sInstancePrefix);
$sInstancePrefix = str_replace(' ', '', $sInstancePrefix);
$this->sInstancePrefix = $sInstancePrefix;
$this->sGeneratedClass = $this->sInstancePrefix . static::DEFAULT_GENERATED_CLASS;
return $this;
}
/**
* Initializes the LifecycleValidator by generating and caching the lifecycles compilation in the $this->sCachePath.$this->sFilename file.
*
* @param DOMNodeList $oNodes
* @throws DOMFormatException
* @throws Exception
*/
public function Init(DOMNodeList $oNodes)
{
// Checking cache path
if ($this->sCachePath === null)
{
$this->sCachePath = utils::GetCachePath();
}
// Building full pathname for file
$sFilePath = $this->sCachePath . $this->sFilename;
// Creating file if not existing
// Note : This is a temporary cache system, it should soon evolve to a cache provider (fs, apc, memcache, ...)
if (!file_exists($sFilePath))
{
// - Build php array from xml
$aProfiles = array();
// This will be used to know which classes have been set, so we can set the missing ones.
$aProfileClasses = array();
// Iterating over the class nodes
foreach ($oNodes as $oClassNode)
{
// retrieving mandatory class id attribute
$sClass = $oClassNode->getAttribute('id');
if ($sClass === '')
{
throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
}
// Iterating over scope nodes of the class
$oScopesNode = $oClassNode->GetOptionalElement('scopes');
if ($oScopesNode !== null)
{
foreach ($oScopesNode->GetNodes('./scope') as $oScopeNode)
{
// Retrieving mandatory scope id attribute
$sScopeId = $oScopeNode->getAttribute('id');
if ($sScopeId === '')
{
throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oScopeNode);
}
// Retrieving the type of query
// Note : This has been disabled as we don't want deny rules for now
// $oOqlViewTypeNode = $oClassNode->GetOptionalElement('oql_view_type');
// $sOqlViewType = ($oOqlViewTypeNode !== null && ($oOqlViewTypeNode->GetText() === static::ENUM_TYPE_RESTRICT)) ? static::ENUM_TYPE_RESTRICT : static::ENUM_TYPE_ALLOW;
$sOqlViewType = static::ENUM_TYPE_ALLOW;
// Retrieving the view query
$oOqlViewNode = $oScopeNode->GetUniqueElement('oql_view');
$sOqlView = $oOqlViewNode->GetText();
if ($sOqlView === null)
{
throw new DOMFormatException('Scope tag in class must have a not empty oql_view tag', null, null, $oScopeNode);
}
// Retrieving the edit query
$oOqlEditNode = $oScopeNode->GetOptionalElement('oql_edit');
$sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
// Retrieving profiles for the scope
$oProfilesNode = $oScopeNode->GetOptionalElement('allowed_profiles');
$aProfilesNames = array();
// If no profile is specified, we consider that it's for ALL the profiles
if (($oProfilesNode === null) || ($oProfilesNode->GetNodes('./allowed_profile')->length === 0))
{
foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
{
$aProfilesNames[] = $aValue['name'];
}
}
else
{
foreach ($oProfilesNode->GetNodes('./allowed_profile') as $oProfileNode)
{
// Retrieving mandatory profile id attribute
$sProfileId = $oProfileNode->getAttribute('id');
if ($sProfileId === '')
{
throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oProfileNode);
}
$aProfilesNames[] = $sProfileId;
}
}
//
foreach ($aProfilesNames as $sProfileName)
{
// Scope profile id
$iProfileId = $this->GetProfileIdFromProfileName($sProfileName);
// Now that we have the queries infos, we are going to build the queries for that profile / class
$sMatrixPrefix = $iProfileId . '_' . $sClass . '_';
// - View query
$oViewFilter = DBSearch::FromOQL($sOqlView);
// ... We have to union the query if this profile has another scope for that class
if (array_key_exists($sMatrixPrefix . static::ENUM_MODE_READ, $aProfiles) && array_key_exists($sOqlViewType, $aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ]))
{
$oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ][$sOqlViewType]);
$aFilters = array($oExistingFilter, $oViewFilter);
$oResFilter = new DBUnionSearch($aFilters);
}
else
{
$oResFilter = $oViewFilter;
}
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ] = array(
$sOqlViewType => $oResFilter->ToOQL()
);
// - Edit query
if ($sOqlEdit !== null)
{
$oEditFilter = DBSearch::FromOQL($sOqlEdit);
// - If the queries are the same, we don't make an intersect, we just reuse the view query
if ($sOqlEdit === $sOqlView)
{
// Do not intersect, edit query is identical to view query
}
else
{
if (($oEditFilter->GetClass() === $oViewFilter->GetClass()) && $oEditFilter->IsAny())
{
$oEditFilter = $oViewFilter;
// Do not intersect, edit query is identical to view query
}
else
{
// Intersect
$oEditFilter = $oViewFilter->Intersect($oEditFilter);
}
}
// ... We have to union the query if this profile has another scope for that class
if (array_key_exists($sMatrixPrefix . static::ENUM_MODE_WRITE, $aProfiles) && array_key_exists($sOqlViewType, $aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE]))
{
$oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE][$sOqlViewType]);
$aFilters = array($oExistingFilter, $oEditFilter);
$oResFilter = new DBUnionSearch($aFilters);
}
else
{
$oResFilter = $oEditFilter;
}
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE] = array(
$sOqlViewType => $oResFilter->ToOQL()
);
}
}
}
$aProfileClasses[] = $sClass;
}
}
// Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
// For each class explicitly given in the scopes, we check if its child classes were also in the scope :
// If not, we add them with the same OQL
foreach ($aProfileClasses as $sProfileClass)
{
foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
{
// If the child class is not in the scope, we are going to try to add it
if (!in_array($sChildClass, $aProfileClasses))
{
foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
{
$iProfileId = $iKey;
foreach (array(static::ENUM_MODE_READ, static::ENUM_MODE_WRITE) as $sAction)
{
// If the current profile has scope for that class in that mode, we duplicate it
if (isset($aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction]))
{
$aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
foreach ($aTmpProfile as $sType => $sOql)
{
$oTmpFilter = DBSearch::FromOQL($sOql);
$oTmpFilter->ChangeClass($sChildClass);
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
}
$aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
}
}
}
}
}
}
// Iterating over the scope nodes
/* foreach ($oNodes as $oScopeNode)
{
// Retrieving mandatory id attribute
$sProfile = $oScopeNode->getAttribute('id');
if ($sProfile === '')
{
throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oScopeNode);
}
// Scope profile id
$iProfileId = $this->GetProfileIdFromProfileName($sProfile);
// This will be used to know which classes have been set, so we can set the missing ones.
$aProfileClasses = array();
// Iterating over the class nodes of the scope
foreach ($oScopeNode->GetUniqueElement('classes')->GetNodes('./class') as $oClassNode)
{
// Retrieving mandatory id attribute
$sClass = $oClassNode->getAttribute('id');
if ($sClass === '')
{
throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
}
// Retrieving the type of query
$oOqlViewTypeNode = $oClassNode->GetOptionalElement('oql_view_type');
$sOqlViewType = ($oOqlViewTypeNode !== null && ($oOqlViewTypeNode->GetText() === static::ENUM_TYPE_RESTRICT)) ? static::ENUM_TYPE_RESTRICT : static::ENUM_TYPE_ALLOW;
// Retrieving the view query
$oOqlViewNode = $oClassNode->GetUniqueElement('oql_view');
$sOqlView = $oOqlViewNode->GetText();
if ($sOqlView === null)
{
throw new DOMFormatException('Class tag in scope must have a not empty oql_view tag', null, null, $oClassNode);
}
// Retrieving the edit query
$oOqlEditNode = $oClassNode->GetOptionalElement('oql_edit');
$sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
// Now that we have the queries infos, we are going to build the queries for that profile / class
$sMatrixPrefix = $iProfileId . '_' . $sClass . '_';
// - View query
$oViewFilter = DBSearch::FromOQL($sOqlView);
$aProfiles[$sMatrixPrefix . 'r'] = array(
$sOqlViewType => $oViewFilter->ToOQL()
);
// - Edit query
if ($sOqlEdit !== null)
{
$oEditFilter = DBSearch::FromOQL($sOqlEdit);
// - If the queries are the same, we don't make an intersect, we just reuse the view query
if ($sOqlEdit === $sOqlView)
{
// Do not intersect, edit query is identical to view query
}
else
{
if (($oEditFilter->GetClass() === $oViewFilter->GetClass()) && $oEditFilter->IsAny())
{
$oEditFilter = $oViewFilter;
// Do not intersect, edit query is identical to view query
}
else
{
// Intersect
$oEditFilter = $oViewFilter->Intersect($oEditFilter);
}
}
$aProfiles[$sMatrixPrefix . 'w'] = array(
$sOqlViewType => $oEditFilter->ToOQL()
);
}
$aProfileClasses[] = $sClass;
}
// Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
// For each class explicitly given in the scopes, we check if its child classes were also in the scope :
// If not, we add them with the same OQL
foreach ($aProfileClasses as $sProfileClass)
{
foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
{
// If the child class is not in the scope, we are going to try to add it
if (!in_array($sChildClass, $aProfileClasses))
{
foreach (array('r', 'w') as $sAction)
{
// If the current profile has scope for that class in that mode, we duplicate it
if (isset($aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction]))
{
$aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
foreach ($aTmpProfile as $sType => $sOql)
{
$oTmpFilter = DBSearch::FromOQL($sOql);
$oTmpFilter->ChangeClass($sChildClass);
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
}
$aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
}
}
}
}
}
} */
// - Build php class
$sPHP = $this->BuildPHPClass($aProfiles);
// - Write file on disk
// - Creating dir if necessary
if (!is_dir($this->sCachePath))
{
mkdir($this->sCachePath, 0777, true);
}
// -- Then creating the file
$ret = file_put_contents($sFilePath, $sPHP);
if ($ret === false)
{
$iLen = strlen($sPHP);
$fFree = @disk_free_space(dirname($sFilePath));
$aErr = error_get_last();
throw new Exception("Failed to write '$sFilePath'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree.");
}
}
if (!class_exists($this->sGeneratedClass))
{
require_once $this->sCachePath . $this->sFilename;
}
}
/**
* Returns the DBSearch for the $sProfile in $iAction for the class $sClass
*
* @param string $sProfile
* @param string $sClass
* @param integer $iAction
* @return DBSearch
*/
public function GetScopeFilterForProfile($sProfile, $sClass, $iAction = null)
{
return $this->GetScopeFilterForProfiles(array($sProfile), $sClass, $iAction);
}
/**
* Returns the DBSearch for the $aProfiles in $iAction for the class $sClass.
* Profiles are a OR condition.
*
* @param array $aProfiles
* @param string $sClass
* @param integer $iAction
* @return DBSearch
*/
public function GetScopeFilterForProfiles($aProfiles, $sClass, $iAction = null)
{
$oSearch = null;
$aAllowSearches = array();
$aRestrictSearches = array();
// Checking the default mode
if ($iAction === null)
{
$iAction = UR_ACTION_READ;
}
// Iterating on profiles to retrieving the different OQLs parts
foreach ($aProfiles as $sProfile)
{
// Retrieving matrix informtions
$iProfileId = $this->GetProfileIdFromProfileName($sProfile);
$sMode = ($iAction === UR_ACTION_READ) ? static::ENUM_MODE_READ : static::ENUM_MODE_WRITE;
// Retrieving profile OQLs
$sScopeValuesClass = $this->sGeneratedClass;
$aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, $sMode);
if ($aProfileMatrix !== null)
{
if (isset($aProfileMatrix['allow']) && $aProfileMatrix['allow'] !== null)
{
$aAllowSearches[] = DBSearch::FromOQL($aProfileMatrix['allow']);
}
if (isset($aProfileMatrix['restrict']) && $aProfileMatrix['restrict'] !== null)
{
$aRestrictSearches[] = DBSearch::FromOQL($aProfileMatrix['restrict']);
}
}
}
// Building the real OQL from all the parts from the differents profiles
for ($i = 0; $i < count($aAllowSearches); $i++)
{
foreach ($aRestrictSearches as $oRestrictSearch)
{
$aAllowSearches[$i] = $aAllowSearches[$i]->Intersect($oRestrictSearch);
}
}
if (count($aAllowSearches) > 0)
{
$oSearch = new DBUnionSearch($aAllowSearches);
$oSearch = $oSearch->RemoveDuplicateQueries();
}
return $oSearch;
}
/**
* Returns the profile id from a string being either a constant or its name.
*
* @param string $sProfile
* @return integer
* @throws Exception
*/
protected function GetProfileIdFromProfileName($sProfile)
{
$iProfileId = null;
// We try to find the profile from its name in order to retrieve it's id
// - If the regular UserRights addon is installed we check the profiles array
if (class_exists('ProfilesConfig'))
{
if (defined($sProfile) && in_array($sProfile, ProfilesConfig::GetProfilesValues()))
{
$iProfileId = constant($sProfile);
}
else
{
foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
{
if ($aValue['name'] === $sProfile)
{
$iProfileId = $iKey;
break;
}
}
}
}
// - Else, we can't find the id from the name as we don't know the used UserRights addon. It has to be a constant
else
{
throw new Exception('Scope validator : Unknown UserRights addon, scope\'s profile must be a constant');
}
// If profile was not found from its name or from a constant, we throw an exception
if ($iProfileId === null)
{
throw new Exception('Scope validator : Could not find "' . $sProfile . '" in the profiles list');
}
return $iProfileId;
}
/**
* Returns a string containing the generated PHP class for the compiled scopes
*
* @param array $aProfiles
* @return string
*/
protected function BuildPHPClass($aProfiles = array())
{
$sProfiles = var_export($aProfiles, true);
$sClassName = $this->sGeneratedClass;
$sPHP = <<<EOF
<?php
// File generated by LifeCycleValidatorHelperHelper
//
// Please do not edit manually
// List of constant scopes
// - used by the portal LifecycleValidatorHelperHelper
//
class $sClassName
{
protected static \$aPROFILES = $sProfiles;
/**
* @param integer \$iProfileId
* @param string \$sClass
*/
public static function GetTransitionProfileScope(\$iProfileId, \$sClass, \$sStimulusCode)
{
\$sQuery = null;
\$sScopeKey = \$iProfileId.'_'.\$sClass.'_'.\$sAction;
if (isset(self::\$aPROFILES[\$sScopeKey]))
{
\$sQuery = self::\$aPROFILES[\$sScopeKey];
}
return \$sQuery;
}
}
EOF;
return $sPHP;
}
}
?>

View File

@@ -0,0 +1,615 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Helper;
use \Exception;
use \DOMNodeList;
use \DOMFormatException;
use \utils;
use \ProfilesConfig;
use \MetaModel;
use \DBSearch;
use \DBUnionSearch;
use \Combodo\iTop\DesignElement;
class ScopeValidatorHelper
{
const ENUM_MODE_READ = 'r';
const ENUM_MODE_WRITE = 'w';
const ENUM_TYPE_ALLOW = 'allow';
const ENUM_TYPE_RESTRICT = 'restrict';
const DEFAULT_GENERATED_CLASS = 'PortalScopesValues';
protected $sCachePath;
protected $sFilename;
protected $sInstancePrefix;
protected $sGeneratedClass;
protected $aProfilesMatrix;
public static function EnumTypeValues()
{
return array(static::ENUM_TYPE_ALLOW, static::ENUM_TYPE_RESTRICT);
}
public function __construct($sFilename, $sCachePath = null)
{
$this->sFilename = $sFilename;
$this->sCachePath = $sCachePath;
$this->sInstancePrefix = '';
$this->sGeneratedClass = static::DEFAULT_GENERATED_CLASS;
$this->aProfilesMatrix = array();
}
/**
* Returns the path where to cache the compiled scopes file
*
* @return string
*/
public function GetCachePath()
{
return $this->sCachePath;
}
/**
* Returns the name of the compiled scopes file
*
* @return string
*/
public function GetFilename()
{
return $this->sFilename;
}
/**
* Returns the instance prefix used for the generated scopes class name
*
* @return string
*/
public function GetInstancePrefix()
{
return $this->sInstancePrefix;
}
/**
* Returns the name of the generated scopes class
*
* @return string
*/
public function GetGeneratedClass()
{
return $this->sGeneratedClass;
}
/**
* Sets the scope validator instance prefix.
*
* This is used to create a unique scope values class in the cache directory (/data/cache-<ENV>) as there can be several instance of the portal.
*
* @param string $sInstancePrefix
* @return \Combodo\iTop\Portal\Helper\ScopeValidatorHelper
*/
public function SetInstancePrefix($sInstancePrefix)
{
$sInstancePrefix = preg_replace('/[-_]/', ' ', $sInstancePrefix);
$sInstancePrefix = ucwords($sInstancePrefix);
$sInstancePrefix = str_replace(' ', '', $sInstancePrefix);
$this->sInstancePrefix = $sInstancePrefix;
$this->sGeneratedClass = $this->sInstancePrefix . static::DEFAULT_GENERATED_CLASS;
return $this;
}
/**
* Initializes the ScopeValidator by generating and caching the scopes compilation in the $this->sCachePath.$this->sFilename file.
*
* @param DOMNodeList $oNodes
* @throws DOMFormatException
* @throws Exception
*/
public function Init(DOMNodeList $oNodes)
{
// Checking cache path
if ($this->sCachePath === null)
{
$this->sCachePath = utils::GetCachePath();
}
// Building full pathname for file
$sFilePath = $this->sCachePath . $this->sFilename;
// Creating file if not existing
// Note : This is a temporary cache system, it should soon evolve to a cache provider (fs, apc, memcache, ...)
if (!file_exists($sFilePath))
{
// - Build php array from xml
$aProfiles = array();
// This will be used to know which classes have been set, so we can set the missing ones.
$aProfileClasses = array();
// Iterating over the class nodes
foreach ($oNodes as $oClassNode)
{
// retrieving mandatory class id attribute
$sClass = $oClassNode->getAttribute('id');
if ($sClass === '')
{
throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
}
// Iterating over scope nodes of the class
$oScopesNode = $oClassNode->GetOptionalElement('scopes');
if ($oScopesNode !== null)
{
foreach ($oScopesNode->GetNodes('./scope') as $oScopeNode)
{
// Retrieving mandatory scope id attribute
$sScopeId = $oScopeNode->getAttribute('id');
if ($sScopeId === '')
{
throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oScopeNode);
}
// Retrieving the type of query
// Note : This has been disabled as we don't want deny rules for now
// $oOqlViewTypeNode = $oClassNode->GetOptionalElement('oql_view_type');
// $sOqlViewType = ($oOqlViewTypeNode !== null && ($oOqlViewTypeNode->GetText() === static::ENUM_TYPE_RESTRICT)) ? static::ENUM_TYPE_RESTRICT : static::ENUM_TYPE_ALLOW;
$sOqlViewType = static::ENUM_TYPE_ALLOW;
// Retrieving the view query
$oOqlViewNode = $oScopeNode->GetUniqueElement('oql_view');
$sOqlView = $oOqlViewNode->GetText();
if ($sOqlView === null)
{
throw new DOMFormatException('Scope tag in class must have a not empty oql_view tag', null, null, $oScopeNode);
}
// Retrieving the edit query
$oOqlEditNode = $oScopeNode->GetOptionalElement('oql_edit');
$sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
// Retrieving profiles for the scope
$oProfilesNode = $oScopeNode->GetOptionalElement('allowed_profiles');
$aProfilesNames = array();
// If no profile is specified, we consider that it's for ALL the profiles
if (($oProfilesNode === null) || ($oProfilesNode->GetNodes('./allowed_profile')->length === 0))
{
foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
{
$aProfilesNames[] = $aValue['name'];
}
}
else
{
foreach ($oProfilesNode->GetNodes('./allowed_profile') as $oProfileNode)
{
// Retrieving mandatory profile id attribute
$sProfileId = $oProfileNode->getAttribute('id');
if ($sProfileId === '')
{
throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oProfileNode);
}
$aProfilesNames[] = $sProfileId;
}
}
//
foreach ($aProfilesNames as $sProfileName)
{
// Scope profile id
$iProfileId = $this->GetProfileIdFromProfileName($sProfileName);
// Now that we have the queries infos, we are going to build the queries for that profile / class
$sMatrixPrefix = $iProfileId . '_' . $sClass . '_';
// - View query
$oViewFilter = DBSearch::FromOQL($sOqlView);
// ... We have to union the query if this profile has another scope for that class
if (array_key_exists($sMatrixPrefix . static::ENUM_MODE_READ, $aProfiles) && array_key_exists($sOqlViewType, $aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ]))
{
$oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ][$sOqlViewType]);
$aFilters = array($oExistingFilter, $oViewFilter);
$oResFilter = new DBUnionSearch($aFilters);
}
else
{
$oResFilter = $oViewFilter;
}
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_READ] = array(
$sOqlViewType => $oResFilter->ToOQL()
);
// - Edit query
if ($sOqlEdit !== null)
{
$oEditFilter = DBSearch::FromOQL($sOqlEdit);
// - If the queries are the same, we don't make an intersect, we just reuse the view query
if ($sOqlEdit === $sOqlView)
{
// Do not intersect, edit query is identical to view query
}
else
{
if (($oEditFilter->GetClass() === $oViewFilter->GetClass()) && $oEditFilter->IsAny())
{
$oEditFilter = $oViewFilter;
// Do not intersect, edit query is identical to view query
}
else
{
// Intersect
$oEditFilter = $oViewFilter->Intersect($oEditFilter);
}
}
// ... We have to union the query if this profile has another scope for that class
if (array_key_exists($sMatrixPrefix . static::ENUM_MODE_WRITE, $aProfiles) && array_key_exists($sOqlViewType, $aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE]))
{
$oExistingFilter = DBSearch::FromOQL($aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE][$sOqlViewType]);
$aFilters = array($oExistingFilter, $oEditFilter);
$oResFilter = new DBUnionSearch($aFilters);
}
else
{
$oResFilter = $oEditFilter;
}
$aProfiles[$sMatrixPrefix . static::ENUM_MODE_WRITE] = array(
$sOqlViewType => $oResFilter->ToOQL()
);
}
}
}
$aProfileClasses[] = $sClass;
}
}
// Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
// For each class explicitly given in the scopes, we check if its child classes were also in the scope :
// If not, we add them with the same OQL
foreach ($aProfileClasses as $sProfileClass)
{
foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
{
// If the child class is not in the scope, we are going to try to add it
if (!in_array($sChildClass, $aProfileClasses))
{
foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
{
$iProfileId = $iKey;
foreach (array(static::ENUM_MODE_READ, static::ENUM_MODE_WRITE) as $sAction)
{
// If the current profile has scope for that class in that mode, we duplicate it
if (isset($aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction]))
{
$aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
foreach ($aTmpProfile as $sType => $sOql)
{
$oTmpFilter = DBSearch::FromOQL($sOql);
$oTmpFilter->ChangeClass($sChildClass);
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
}
$aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
}
}
}
}
}
}
// Iterating over the scope nodes
/* foreach ($oNodes as $oScopeNode)
{
// Retrieving mandatory id attribute
$sProfile = $oScopeNode->getAttribute('id');
if ($sProfile === '')
{
throw new DOMFormatException('Scope tag must have an id attribute.', null, null, $oScopeNode);
}
// Scope profile id
$iProfileId = $this->GetProfileIdFromProfileName($sProfile);
// This will be used to know which classes have been set, so we can set the missing ones.
$aProfileClasses = array();
// Iterating over the class nodes of the scope
foreach ($oScopeNode->GetUniqueElement('classes')->GetNodes('./class') as $oClassNode)
{
// Retrieving mandatory id attribute
$sClass = $oClassNode->getAttribute('id');
if ($sClass === '')
{
throw new DOMFormatException('Class tag must have an id attribute.', null, null, $oClassNode);
}
// Retrieving the type of query
$oOqlViewTypeNode = $oClassNode->GetOptionalElement('oql_view_type');
$sOqlViewType = ($oOqlViewTypeNode !== null && ($oOqlViewTypeNode->GetText() === static::ENUM_TYPE_RESTRICT)) ? static::ENUM_TYPE_RESTRICT : static::ENUM_TYPE_ALLOW;
// Retrieving the view query
$oOqlViewNode = $oClassNode->GetUniqueElement('oql_view');
$sOqlView = $oOqlViewNode->GetText();
if ($sOqlView === null)
{
throw new DOMFormatException('Class tag in scope must have a not empty oql_view tag', null, null, $oClassNode);
}
// Retrieving the edit query
$oOqlEditNode = $oClassNode->GetOptionalElement('oql_edit');
$sOqlEdit = ( ($oOqlEditNode !== null) && ($oOqlEditNode->GetText() !== null) ) ? $oOqlEditNode->GetText() : null;
// Now that we have the queries infos, we are going to build the queries for that profile / class
$sMatrixPrefix = $iProfileId . '_' . $sClass . '_';
// - View query
$oViewFilter = DBSearch::FromOQL($sOqlView);
$aProfiles[$sMatrixPrefix . 'r'] = array(
$sOqlViewType => $oViewFilter->ToOQL()
);
// - Edit query
if ($sOqlEdit !== null)
{
$oEditFilter = DBSearch::FromOQL($sOqlEdit);
// - If the queries are the same, we don't make an intersect, we just reuse the view query
if ($sOqlEdit === $sOqlView)
{
// Do not intersect, edit query is identical to view query
}
else
{
if (($oEditFilter->GetClass() === $oViewFilter->GetClass()) && $oEditFilter->IsAny())
{
$oEditFilter = $oViewFilter;
// Do not intersect, edit query is identical to view query
}
else
{
// Intersect
$oEditFilter = $oViewFilter->Intersect($oEditFilter);
}
}
$aProfiles[$sMatrixPrefix . 'w'] = array(
$sOqlViewType => $oEditFilter->ToOQL()
);
}
$aProfileClasses[] = $sClass;
}
// Filling the array with missing classes from MetaModel, so we can have an inheritance principle on the scope
// For each class explicitly given in the scopes, we check if its child classes were also in the scope :
// If not, we add them with the same OQL
foreach ($aProfileClasses as $sProfileClass)
{
foreach (MetaModel::EnumChildClasses($sProfileClass) as $sChildClass)
{
// If the child class is not in the scope, we are going to try to add it
if (!in_array($sChildClass, $aProfileClasses))
{
foreach (array('r', 'w') as $sAction)
{
// If the current profile has scope for that class in that mode, we duplicate it
if (isset($aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction]))
{
$aTmpProfile = $aProfiles[$iProfileId . '_' . $sProfileClass . '_' . $sAction];
foreach ($aTmpProfile as $sType => $sOql)
{
$oTmpFilter = DBSearch::FromOQL($sOql);
$oTmpFilter->ChangeClass($sChildClass);
$aTmpProfile[$sType] = $oTmpFilter->ToOQL();
}
$aProfiles[$iProfileId . '_' . $sChildClass . '_' . $sAction] = $aTmpProfile;
}
}
}
}
}
} */
// - Build php class
$sPHP = $this->BuildPHPClass($aProfiles);
// - Write file on disk
// - Creating dir if necessary
if (!is_dir($this->sCachePath))
{
mkdir($this->sCachePath, 0777, true);
}
// -- Then creating the file
$ret = file_put_contents($sFilePath, $sPHP);
if ($ret === false)
{
$iLen = strlen($sPHP);
$fFree = @disk_free_space(dirname($sFilePath));
$aErr = error_get_last();
throw new Exception("Failed to write '$sFilePath'. Last error: '{$aErr['message']}', content to write: $iLen bytes, available free space on disk: $fFree.");
}
}
if (!class_exists($this->sGeneratedClass))
{
require_once $this->sCachePath . $this->sFilename;
}
}
/**
* Returns the DBSearch for the $sProfile in $iAction for the class $sClass
*
* @param string $sProfile
* @param string $sClass
* @param integer $iAction
* @return DBSearch
*/
public function GetScopeFilterForProfile($sProfile, $sClass, $iAction = null)
{
return $this->GetScopeFilterForProfiles(array($sProfile), $sClass, $iAction);
}
/**
* Returns the DBSearch for the $aProfiles in $iAction for the class $sClass.
* Profiles are a OR condition.
*
* @param array $aProfiles
* @param string $sClass
* @param integer $iAction
* @return DBSearch
*/
public function GetScopeFilterForProfiles($aProfiles, $sClass, $iAction = null)
{
$oSearch = null;
$aAllowSearches = array();
$aRestrictSearches = array();
// Checking the default mode
if ($iAction === null)
{
$iAction = UR_ACTION_READ;
}
// Iterating on profiles to retrieving the different OQLs parts
foreach ($aProfiles as $sProfile)
{
// Retrieving matrix informtions
$iProfileId = $this->GetProfileIdFromProfileName($sProfile);
$sMode = ($iAction === UR_ACTION_READ) ? static::ENUM_MODE_READ : static::ENUM_MODE_WRITE;
// Retrieving profile OQLs
$sScopeValuesClass = $this->sGeneratedClass;
$aProfileMatrix = $sScopeValuesClass::GetProfileScope($iProfileId, $sClass, $sMode);
if ($aProfileMatrix !== null)
{
if (isset($aProfileMatrix['allow']) && $aProfileMatrix['allow'] !== null)
{
$aAllowSearches[] = DBSearch::FromOQL($aProfileMatrix['allow']);
}
if (isset($aProfileMatrix['restrict']) && $aProfileMatrix['restrict'] !== null)
{
$aRestrictSearches[] = DBSearch::FromOQL($aProfileMatrix['restrict']);
}
}
}
// Building the real OQL from all the parts from the differents profiles
for ($i = 0; $i < count($aAllowSearches); $i++)
{
foreach ($aRestrictSearches as $oRestrictSearch)
{
$aAllowSearches[$i] = $aAllowSearches[$i]->Intersect($oRestrictSearch);
}
}
if (count($aAllowSearches) > 0)
{
$oSearch = new DBUnionSearch($aAllowSearches);
$oSearch = $oSearch->RemoveDuplicateQueries();
}
return $oSearch;
}
/**
* Returns the profile id from a string being either a constant or its name.
*
* @param string $sProfile
* @return integer
* @throws Exception
*/
protected function GetProfileIdFromProfileName($sProfile)
{
$iProfileId = null;
// We try to find the profile from its name in order to retrieve it's id
// - If the regular UserRights addon is installed we check the profiles array
if (class_exists('ProfilesConfig'))
{
if (defined($sProfile) && in_array($sProfile, ProfilesConfig::GetProfilesValues()))
{
$iProfileId = constant($sProfile);
}
else
{
foreach (ProfilesConfig::GetProfilesValues() as $iKey => $aValue)
{
if ($aValue['name'] === $sProfile)
{
$iProfileId = $iKey;
break;
}
}
}
}
// - Else, we can't find the id from the name as we don't know the used UserRights addon. It has to be a constant
else
{
throw new Exception('Scope validator : Unknown UserRights addon, scope\'s profile must be a constant');
}
// If profile was not found from its name or from a constant, we throw an exception
if ($iProfileId === null)
{
throw new Exception('Scope validator : Could not find "' . $sProfile . '" in the profiles list');
}
return $iProfileId;
}
/**
* Returns a string containing the generated PHP class for the compiled scopes
*
* @param array $aProfiles
* @return string
*/
protected function BuildPHPClass($aProfiles = array())
{
$sProfiles = var_export($aProfiles, true);
$sClassName = $this->sGeneratedClass;
$sPHP = <<<EOF
<?php
// File generated by ScopeValidatorHelperHelper
//
// Please do not edit manually
// List of constant scopes
// - used by the portal ScopeValidatorHelperHelper
//
class $sClassName
{
protected static \$aPROFILES = $sProfiles;
/**
* @param integer \$iProfileId
* @param string \$sClass
* @param string \$sAction 'r'|'w'
*/
public static function GetProfileScope(\$iProfileId, \$sClass, \$sAction)
{
\$sQuery = null;
\$sScopeKey = \$iProfileId.'_'.\$sClass.'_'.\$sAction;
if (isset(self::\$aPROFILES[\$sScopeKey]))
{
\$sQuery = self::\$aPROFILES[\$sScopeKey];
}
return \$sQuery;
}
}
EOF;
return $sPHP;
}
}
?>

View File

@@ -0,0 +1,126 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Helper;
use \Exception;
use \Silex\Application;
use \utils;
use \UserRights;
use \Dict;
use \MetaModel;
use \DBObjectSet;
use \FieldExpression;
use \VariableExpression;
use \BinaryExpression;
use \Combodo\iTop\Portal\Helper\ScopeValidatorHelper;
/**
* SecurityHelper class
*
* Handle security checks through the different layers (portal scopes, iTop silos, user rights)
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class SecurityHelper
{
/**
* Returns true if the current user is allowed to do the $sAction on an $sObjectClass object (with optionnal $sObjectId id)
*
* @param Silex\Application $oApp
* @param string $sAction Must be in UR_ACTION_READ|UR_ACTION_MODIFY|UR_ACTION_CREATE
* @param string $sObjectClass
* @param string $sObjectId
* @return boolean
*/
public static function IsActionAllowed(Application $oApp, $sAction, $sObjectClass, $sObjectId = null)
{
// Checking action type
if (!in_array($sAction, array(UR_ACTION_READ, UR_ACTION_MODIFY, UR_ACTION_CREATE)))
{
return false;
}
// Checking the scopes layer
// - Transforming scope action as there is only 2 values
$sScopeAction = ($sAction === UR_ACTION_READ) ? UR_ACTION_READ : UR_ACTION_MODIFY;
// - Retrieving the query
$oScopeQuery = $oApp['scope_validator']->GetScopeFilterForProfiles(UserRights::ListProfiles(), $sObjectClass, $sScopeAction);
if ($oScopeQuery === null)
{
return false;
}
// - If action != create we do some additionnal checks
if ($sAction !== UR_ACTION_CREATE)
{
// - Adding object id to the query if specified
if ($sObjectId !== null)
{
// - Adding expression
$sObjectKeyAtt = MetaModel::DBGetKey($sObjectClass);
$oFieldExp = new FieldExpression($sObjectKeyAtt, $oScopeQuery->GetClassAlias());
$oBinExp = new BinaryExpression($oFieldExp, '=', new VariableExpression('object_id'));
$oScopeQuery->AddConditionExpression($oBinExp);
// - Setting value
$aQueryParams = $oScopeQuery->GetInternalParams();
$aQueryParams['object_id'] = $sObjectId;
$oScopeQuery->SetInternalParams($aQueryParams);
unset($aQueryParams);
}
// - Checking if query result is null
$oSet = new DBObjectSet($oScopeQuery);
if ($oSet->Count() === 0)
{
return false;
}
// Checking if the cmdbAbstractObject exists if id is specified
if ($sObjectId !== null)
{
$oObject = MetaModel::GetObject($sObjectClass, $sObjectId, false /* MustBeFound */);
if ($oObject === null)
{
return false;
}
unset($oObject);
}
}
// Checking reading security layer. The object could be listed, check if it is actually allowed to view it
if (UserRights::IsActionAllowed($sObjectClass, $sAction) == UR_ALLOWED_NO)
{
// For security reasons, we don't want to give the user too many informations on why he cannot access the object.
//throw new SecurityException('User not allowed to view this object', array('class' => $sObjectClass, 'id' => $sObjectId));
return false;
}
return true;
}
public static function IsStimulusAllowed(Application $oApp, $sStimulusCode, $sObjectClass, $oInstanceSet = null)
{
$aStimuli = Metamodel::EnumStimuli($sObjectClass);
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sObjectClass, $sStimulusCode, $oInstanceSet) : UR_ALLOWED_NO;
}
}
?>

View File

@@ -0,0 +1,60 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Helper;
use Symfony\Component\Routing\Generator\UrlGenerator as SymfonyUrlGenerator;
use utils;
/**
* Based on Symfony UrlGenerator
*
* UrlGenerator can generate a URL or a path for any route in the RouteCollection
* based on the passed parameters.
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*
* @api
*/
class UrlGenerator extends SymfonyUrlGenerator
{
/**
* Overloading of the parent function to add the $_REQUEST parameters to the url parameters.
* This is used to keep additionnal parameters in the url, especially when portal is accessed from the /pages/exec.php
*
* Note : As of now, it only adds the exec_module and exec_page parameters. Any other parameter will be ignored.
*
* @return string
*/
public function generate($name, $parameters = array(), $referenceType = SymfonyUrlGenerator::ABSOLUTE_PATH)
{
$sExecModule = utils::ReadParam('exec_module', '', false, 'string');
$sExecPage = utils::ReadParam('exec_page', '', false, 'string');
if ($sExecModule !== '' && $sExecPage !== '')
{
$parameters['exec_module'] = $sExecModule;
$parameters['exec_page'] = $sExecPage;
}
return parent::generate($name, $parameters, $referenceType);
}
}
?>

View File

@@ -0,0 +1,51 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Combodo\iTop\Portal\Helper\ContextManipulatorHelper;
/**
* ContextManipulatorHelper service provider
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class ContextManipulatorServiceProvider implements ServiceProviderInterface
{
public function register(Application $oApp)
{
$oApp['context_manipulator'] = $oApp->share(function ($oApp)
{
$oApp->flush();
$oContextManipulatorHelper = new ContextManipulatorHelper();
return $oContextManipulatorHelper;
});
}
public function boot(Application $oApp)
{
}
}

View File

@@ -0,0 +1,55 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Combodo\iTop\Portal\Helper\ScopeValidatorHelper;
/**
* ScopeValidatorHelper service provider
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class ScopeValidatorServiceProvider implements ServiceProviderInterface
{
public function register(Application $oApp)
{
$oApp['scope_validator'] = $oApp->share(function ($oApp)
{
$oApp->flush();
$oScopeValidatorHelper = new ScopeValidatorHelper($oApp['scope_validator.scopes_filename'], $oApp['scope_validator.scopes_path']);
if (isset($oApp['scope_validator.instance_name']))
{
$oScopeValidatorHelper->SetInstancePrefix($oApp['scope_validator.instance_name'] . '-');
}
return $oScopeValidatorHelper;
});
}
public function boot(Application $oApp)
{
}
}

View File

@@ -0,0 +1,49 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Provider;
use Silex\Application;
use Silex\ServiceProviderInterface;
use Combodo\iTop\Portal\Helper\UrlGenerator;
/**
* Based on Symfony Routing component Provider for URL generation.
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class UrlGeneratorServiceProvider implements ServiceProviderInterface
{
public function register(Application $oApp)
{
$oApp['url_generator'] = $oApp->share(function ($oApp)
{
$oApp->flush();
return new UrlGenerator($oApp['routes'], $oApp['request_context']);
});
}
public function boot(Application $oApp)
{
}
}

View File

@@ -0,0 +1,138 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
/**
* AbstractRouter class is where URLs are defined with their callback, parameters and constraints (assertions).
* It allows us to have URL pattern at one place only and to generate them anywhere in the code, avoiding to maintain URLs in multiple places.
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
abstract class AbstractRouter
{
/**
* List of routes for that Router.
*
* Each route is defined as an associative array and can have the following parameters :
* - pattern : URL pattern with its parameters names (eg: '/{sBrickId}/browse/{sBrowseMode}')
* - hash : String to append to the URL with an '#' (eg: 'modal-popup' will append '#modal-popup' to the above URL)
* - callback : Function to be called for that route, usally in a Controller. (eg: 'Combodo\\iTop\\Portal\\Controller\\CreateBrickController::DisplayAction')
* - bind : Unique name of the route, must not contain blanks. Usually lowercase with underscore (eg: 'p_browse_brick')
* - asserts : Associative array of assertions to check for the pattern parameters (eg: array( 'sBrowseMode' => 'list|tree'))
* - values : Associative array of default values for the pattern parameters (eg: array('sBrowseMode' => 'tree'))
*
* @var array
*/
static $aRoutes = array();
/**
* Returns routes of the current AbstractRouter defined in $aRoutes.
*
* @return array
*/
static function GetRoutes()
{
return static::$aRoutes;
}
/**
* Returns the route named $name of the current AbstractRouter.
* Throws an exception if not found.
*
* @param string $name
* @return array
* @throws \Exception
*/
static function GetRoute($name)
{
$bFound = false;
$aFoundRoute = array();
foreach (static::$aRoutes as $aRoute)
{
if (isset($aRoute['bind']) && $aRoute['bind'] === $name)
{
$bFound = true;
$aFoundRoute = $aRoute;
break;
}
}
if (!$bFound)
{
throw new \Exception('Unknown route "' . $name . '" for ' . get_class() . '');
}
return $aRoute;
}
/**
* Registers all routes of the current AbstractRouter to the Application $oApp.
*
* @param Application $oApp
* @return int Number of succesfully registered routes
* @throws \Exception
*/
static function RegisterAllRoutes(Application $oApp)
{
$iCounter = 0;
foreach (static::$aRoutes as $aRoute)
{
// Check if we have the base parameters to register the route
if (!isset($aRoute['pattern']) || !isset($aRoute['callback']))
{
throw new \Exception('Unable to register routes from ' . get_class() . ', some parameters are missing.');
}
// Registering base route
$controller = $oApp->match($aRoute['pattern'], $aRoute['callback']);
// Checking if route has optionnal parameters
if (isset($aRoute['bind']))
{
$controller->bind($aRoute['bind']);
}
if (isset($aRoute['asserts']))
{
foreach ($aRoute['asserts'] as $sKey => $sValue)
{
$controller->assert($sKey, $sValue);
}
}
if (isset($aRoute['values']))
{
foreach ($aRoute['values'] as $sKey => $sValue)
{
$controller->value($sKey, $sValue);
}
}
$iCounter++;
}
return $iCounter;
}
}
?>

View File

@@ -0,0 +1,67 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should havze received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class BrowseBrickRouter extends AbstractRouter
{
static $aRoutes = array(
// We don't set asserts for sBrowseMode on that route, as it the generic one, it can be extended by another brick.
array('pattern' => '/browse/{sBrickId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\BrowseBrickController::DisplayAction',
'bind' => 'p_browse_brick'
),
array('pattern' => '/browse/{sBrickId}/{sBrowseMode}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\BrowseBrickController::DisplayAction',
'bind' => 'p_browse_brick_mode'
),
array('pattern' => '/browse/{sBrickId}/list/page/{iPageNumber}/show/{iCountPerPage}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\BrowseBrickController::DisplayAction',
'bind' => 'p_browse_brick_mode_list',
'asserts' => array(
'sBrowseMode' => 'list',
'iPageNumber' => '\d+',
'iCountPerPage' => '\d+'
),
'values' => array(
'sBrowseMode' => 'list',
'sDataLoading' => 'lazy',
'iPageNumber' => '1',
'iCountPerPage' => '20'
)
),
array('pattern' => '/browse/{sBrickId}/tree/expand/{sLevelAlias}/{sNodeId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\BrowseBrickController::DisplayAction',
'bind' => 'p_browse_brick_mode_tree',
'asserts' => array(
'sBrowseMode' => 'tree'
),
'values' => array(
'sBrowseMode' => 'tree',
'sDataLoading' => 'lazy',
'sNodeId' => null
)
),
);
}
?>

View File

@@ -0,0 +1,34 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class CreateBrickRouter extends AbstractRouter
{
static $aRoutes = array(
array('pattern' => '/create/{sBrickId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\CreateBrickController::DisplayAction',
'bind' => 'p_create_brick')
);
}
?>

View File

@@ -0,0 +1,40 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class DefaultRouter extends AbstractRouter
{
static $aRoutes = array(
array('pattern' => '/',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\DefaultController::homeAction',
'bind' => 'p_home'),
// // Example route
// array('pattern' => '/url-pattern',
// 'hash' => 'string-to-be-append-to-the-pattern-after-a-#',
// 'navigation_menu_attr' => array('id' => 'link_id', 'rel' => 'foo'),
// 'callback' => 'Combodo\\iTop\\Portal\\Controller\\DefaultController::exampleAction',
// 'bind' => 'p_example')
);
}
?>

View File

@@ -0,0 +1,49 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class ManageBrickRouter extends AbstractRouter
{
static $aRoutes = array(
array('pattern' => '/manage/{sBrickId}/{sGroupingTab}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::DisplayAction',
'bind' => 'p_manage_brick',
'values' => array('sGroupingTab' => null)
),
array('pattern' => '/manage/{sBrickId}/{sGroupingTab}/{sGroupingArea}/page/{iPageNumber}/show/{iCountPerPage}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ManageBrickController::DisplayAction',
'bind' => 'p_manage_brick_lazy',
'asserts' => array(
'iPageNumber' => '\d+',
'iCountPerPage' => '\d+'
),
'values' => array(
'sDataLoading' => 'lazy',
'iPageNumber' => '1',
'iCountPerPage' => '20'
)
)
);
}
?>

View File

@@ -0,0 +1,103 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class ObjectRouter extends AbstractRouter
{
static $aRoutes = array(
array('pattern' => '/object/create/{sObjectClass}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::CreateAction',
'bind' => 'p_object_create'
),
array('pattern' => '/object/create-from-factory/{sObjectClass}/{sObjectId}/{sEncodedMethodName}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::CreateFromFactoryAction',
'bind' => 'p_object_create_from_factory'
),
array('pattern' => '/object/edit/{sObjectClass}/{sObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::EditAction',
'bind' => 'p_object_edit'
),
array('pattern' => '/object/view/{sObjectClass}/{sObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::ViewAction',
'bind' => 'p_object_view'
),
array('pattern' => '/object/apply-stimulus/{sStimulusCode}/{sObjectClass}/{sObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::ApplyStimulusAction',
'bind' => 'p_object_apply_stimulus'
),
array('pattern' => '/object/attachment/add',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::AttachmentAction',
'bind' => 'p_object_attachment_add'
),
array('pattern' => '/object/attachment/download/{sAttachmentId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::AttachmentAction',
'bind' => 'p_object_attachment_download',
'values' => array(
'sOperation' => 'download'
)
),
array('pattern' => '/object/search',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::SearchRegularAction',
'bind' => 'p_object_search_regular'
),
array('pattern' => '/object/search/from-attribute/{sTargetAttCode}/{sHostObjectClass}/{sHostObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::SearchFromAttributeAction',
'bind' => 'p_object_search_from_attribute',
'values' => array(
'sHostObjectClass' => null,
'sHostObjectId' => null
)
),
array('pattern' => '/object/search/autocomplete/{sTargetAttCode}/{sHostObjectClass}/{sHostObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::SearchAutocompleteAction',
'bind' => 'p_object_search_autocomplete',
'values' => array(
'sHostObjectClass' => null,
'sHostObjectId' => null
)
),
array('pattern' => '/object/search/hierarchy/{sTargetAttCode}/{sHostObjectClass}/{sHostObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::SearchHierarchyAction',
'bind' => 'p_object_search_hierarchy',
'values' => array(
'sHostObjectClass' => null,
'sHostObjectId' => null
)
),
array('pattern' => '/object/search/{sMode}/{sTargetAttCode}/{sHostObjectClass}/{sHostObjectId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::SearchAction',
'bind' => 'p_object_search_generic',
'values' => array(
'sMode' => '-sMode-',
'sHostObjectClass' => null,
'sHostObjectId' => null
)
),
array('pattern' => '/object/get-informations/json',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\ObjectController::GetInformationsAsJsonAction',
'bind' => 'p_object_get_informations_json',
),
);
}
?>

View File

@@ -0,0 +1,37 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Portal\Router;
use Silex\Application;
class UserProfileRouter extends AbstractRouter
{
static $aRoutes = array(
array('pattern' => '/user/{sBrickId}',
'callback' => 'Combodo\\iTop\\Portal\\Controller\\UserProfileBrickController::DisplayAction',
'bind' => 'p_user_profile_brick',
'values' => array(
'sBrickId' => null
))
);
}
?>

View File

@@ -0,0 +1,35 @@
{# itop-portal-base/portal/src/views/bricks/browse/layout.html.twig #}
{# Browse brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
{% block pMainHeaderTitle %}
{{ oBrick.GetTitle()|dict_s }}
{% endblock %}
{% block pMainHeaderActions %}
{% if aBrowseButtons|length > 1 %}
<div class="row">
<div class="col-sm-12">
<div class="btn-group btn-group-justified btn-group-sm">
{% for sBrowseButton in aBrowseButtons %}
<a href="{{ app.url_generator.generate('p_browse_brick_mode', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseButton}) }}" class="btn btn-default {% if sBrowseMode == sBrowseButton %}active{% endif %}">{{ ('Brick:Portal:Browse:Mode:'~sBrowseButton|capitalize)|dict_s }}</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block pMainContentHolder%}
{% if iItemsCount > 0 %}
<div class="panel panel-default">
{% block bBrowseMainContent %}
{% endblock %}
</div>
{% else %}
<div class="panel panel-info">
<div class="panel-heading">Information</div>
<div class="panel-body">Il n'y aucune donnée à afficher sur cette page.</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,266 @@
{# itop-portal-base/portal/src/views/bricks/browse/mode_list.html.twig #}
{# Browse brick list mode layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/browse/layout.html.twig' %}
{% block bBrowseMainContent%}
<table id="brick-content-table" class="table table-striped table-bordered responsive" cellspacing="0" width="100%">
<tbody>
</tbody>
</table>
{% endblock %}
{% block pPageLiveScripts %}
{{ parent() }}
<script type="text/javascript">
var sBrowseMode = '{{ sBrowseMode }}';
var sDataLoading = '{{ sDataLoading }}';
var oLevelsProperties = {{ aLevelsProperties|raw }};
var oRawDatas = {{ aItems|raw }};
var oTable;
// Used for ajax throttling
var iSearchThrottle = 600;
var oKeyTimeout;
var aKeyTimeoutFilteredKeys = [16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40]; // Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
// Show a loader inside the table
var showTableLoader = function()
{
$('#brick-content-table > tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
};
// Columns definition for the table from the oLevelsProperties
var getColumnsDefinition = function()
{
var aColumnsDefinition = [];
for(sKey in oLevelsProperties)
{
// Level main column
aColumnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": oLevelsProperties[sKey].title,
"defaultContent": "",
"type": "html",
"data": oLevelsProperties[sKey].alias,
"render": function(data, type, row){
var cellElem;
var levelAltId = data.level_alias+'_'+data.id;
var levelActions;
var levelActionsKeys;
var drilldownActionIndex;
var levelPrimaryAction;
var url = '';
console.log(data, row);
// Preparing actions on the cell
levelActions = oLevelsProperties[data.level_alias].actions;
// - Removing explicit (not default) drilldown action as it has no prupose on that browse mode
delete levelActions['{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}'];
// - Removing implciit (default) drilldown action
if( (levelActions['default'] !== undefined) && (levelActions['default'].type === '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}') )
{
delete levelActions['default'];
}
levelActionsKeys = Object.keys(levelActions);
// Preparing the cell data
cellElem = (levelActionsKeys.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.attr('data-item-id', data.id).attr('data-level-alias', data.level_alias);
// Building tooltip for the node
// We have to concatenate the HTML as we return the raw HTML of the cell. If we did a jQuery.insertAfter, the tooltip would not be returned.
// For the same reason, tooltip widget is created in "drawCallback" instead of here.
if( (data.tooltip !== undefined) && (data.tooltip !== ''))
{
cellElem.html( $('<span></span>').attr('title', data.tooltip).attr('data-toggle', 'tooltip').text(data.name).prop('outerHTML') );
}
else
{
cellElem.text(data.name);
}
// Building actions
if(levelActionsKeys.length > 0)
{
// - Primary action (click on item)
levelPrimaryAction = levelActions[levelActionsKeys[0]];
switch(levelPrimaryAction.type)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = levelPrimaryAction.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = addParameterToUrl(url, 'ar_token', data.action_rules_token[levelPrimaryAction.type]);
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+levelPrimaryAction.type+'" not implemented');
break;
}
// - Secondary actions
if(levelActionsKeys.length > 1)
{
var actionsElem = $('<div></div>').addClass('pull-right group-actions');
cellElem.append(actionsElem);
// Preparing secondary actions for small screens
var actionsSSTogglerElem = $('<a class="glyphicon glyphicon-menu-hamburger" data-toggle="collapse" data-target="#item-actions-menu-'+levelAltId+'"></a>');
var actionsSSMenuElem = $('<div id="item-actions-menu-'+levelAltId+'" class="item-action-wrapper panel panel-default"></div>');
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>');
actionsSSMenuElem.append(actionsSSMenuContainerElem);
actionsElem.append(actionsSSTogglerElem);
actionsElem.append(actionsSSMenuElem);
var actionsButtons = {};
// Fill actionsButtons with all actions but the primary
for(j = 1; j < levelActionsKeys.length; j++)
{
actionsButtons[levelActionsKeys[j]] = levelActions[levelActionsKeys[j]];
}
for(j in actionsButtons)
{
var action = actionsButtons[j];
var actionElem = $('<a></a>');
switch(action.type)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url).text(action.title);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url).text(action.title);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = action.url.replace(/-objectClass-/, data.class).replace(/-objectId-/, data.id);
url = addParameterToUrl(url, 'ar_token', data.action_rules_token[action.type]);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url).text(action.title);
break;
default:
console.log('Action "'+action.type+'" not implemented for secondary action');
break;
}
actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem.clone()) );
}
}
}
return cellElem.prop('outerHTML');
},
});
// Level's fields columns
if(oLevelsProperties[sKey].fields !== undefined)
{
for(var i in oLevelsProperties[sKey].fields)
{
aColumnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": false,
"title": oLevelsProperties[sKey].fields[i].label,
"defaultContent": "",
"type": "html",
"data": oLevelsProperties[sKey].alias+".fields."+oLevelsProperties[sKey].fields[i].code
});
}
}
}
return aColumnsDefinition;
};
$(document).ready(function()
{
showTableLoader();
// Note : Those options should be externalized in an library so we can use them on any DataTables for the portal.
// We would just have to override / complete the necessary elements
oTable = $('#brick-content-table').DataTable({
"language": {
"processing": "{{ 'Portal:Datatables:Language:Processing'|dict_s }}",
"search": "{{ 'Portal:Datatables:Language:Search'|dict_s }}",
"lengthMenu": "{{ 'Portal:Datatables:Language:LengthMenu'|dict_s }}",
"zeroRecords": "{{ 'Portal:Datatables:Language:ZeroRecords'|dict_s }}",
"info": "{{ 'Portal:Datatables:Language:Info'|dict_s }}",
"infoEmpty": "{{ 'Portal:Datatables:Language:InfoEmpty'|dict_s }}",
"infoFiltered": "({{ 'Portal:Datatables:Language:InfoFiltered'|dict_s }})",
"emptyTable": "{{ 'Portal:Datatables:Language:EmptyTable'|dict_s }}",
"paginate": {
"first": "{{ 'Portal:Datatables:Language:Paginate:First'|dict_s }}",
"previous": "{{ 'Portal:Datatables:Language:Paginate:Previous'|dict_s }}",
"next": "{{ 'Portal:Datatables:Language:Paginate:Next'|dict_s }}",
"last": "{{ 'Portal:Datatables:Language:Paginate:Last'|dict_s }}"
},
"aria": {
"sortAscending": ": {{ 'Portal:Datatables:Language:Sort:Ascending'|dict_s }}",
"sortDescending": ": {{ 'Portal:Datatables:Language:Sort:Descending'|dict_s }}"
}
},
"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]],
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::DEFAULT_COUNT_PER_PAGE_LIST') }},
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
"columns": getColumnsDefinition(),
"drawCallback": function(settings){
// Tooltip has to been created here, as the render callback only returns a string, not an object.
$(this).find('[data-toggle="tooltip"]').tooltip({container: 'body', html: true, trigger: 'hover', placement: 'right'}); // container option is necessary when in a table
},
{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
"data": oRawDatas,
{% else %}
"processing": true,
"serverSide": true,
"ajax": {
"url": "{{ app.url_generator.generate('p_browse_brick_mode', {'sBrickId': sBrickId, 'sBrowseMode': constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_BROWSE_MODE_LIST')})|raw }}",
"data": function(d){
d.iPageNumber = Math.floor(d.start/d.length) + 1;
d.iCountPerPage = d.length;
d.columns = null;
d.orders = null;
if(d.search.value)
{
d.sSearchValue = d.search.value;
}
}
}
{% endif %}
});
// Overrides filter input to apply throttle. Otherwise, an ajax request is send each time a key is pressed
// Also removes accents from search string
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
$('#brick-content-table_filter input').off().on('keyup', function(event){
var me = this;
// We trigger the search only if those keys where not pressed
if(aKeyTimeoutFilteredKeys.indexOf(event.which) < 0)
{
clearTimeout(oKeyTimeout);
oKeyTimeout = setTimeout(function() {
oTable.search(me.value.latinise()).draw();
}, iSearchThrottle);
}
});
// Shows a loader in the table when processing
$('#brick-content-table').on('processing.dt', function(event, settings, processing){
if(processing === true)
{
showTableLoader();
}
});
// Auto collapse item actions popup
$('body').click(function(){
$('table .item-action-wrapper.collapse.in').collapse('hide');
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,388 @@
{# itop-portal-base/portal/src/views/bricks/browse/mode_tree.html.twig #}
{# Browse brick tree mode layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/browse/layout.html.twig' %}
{#
Documentation :
#brick_content_tree is populated by JS
#brick_search_field works differently regarding the brick data loading mode :
- When set to "full", all the tree is already populated and the search looks through it.
- When set to "lazy", if the tree is partially loaded, the search will first load it completely, then only perform the search.
#}
{% block pPageScripts %}
{{ parent() }}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery-treelistfilter/js/TreeListFilter.js"></script>
{% endblock %}
{% block bBrowseMainContent %}
<div class="row" id="brick_content_toolbar">
<div class="col-xs-4 col-sm-2 col-lg-1">
<div class="btn-group btn-group-justified btn-group-sm">
<a href="#" class="btn btn-default" id="btn-collapse-all" title="{{ 'Brick:Portal:Browse:Tree:CollapseAll'|dict_s }}">-</a>
<a href="#" class="btn btn-default" id="btn-expand-all" title="{{ 'Brick:Portal:Browse:Tree:ExpandAll'|dict_s }}">+</a>
</div>
</div>
<div class="col-xs-8 col-sm-10 col-lg-11 text-right">
<label>Filtrer :<input type="search" class="form-control input-sm" id="brick_search_field" placeholder="" aria-controls="brick_main_table"></label>
</div>
</div>
<ul class="list-group" id="brick_content_tree" data-level-id="L">
</ul>
<div id="brick_content_empty" class="text-center">
{{ 'Brick:Portal:Browse:Filter:NoData'|dict_s }}
</div>
<div id="brick_tree_overlay">
<div class="overlay_content">
<div class="content_loader">
<div class="icon glyphicon glyphicon-refresh"></div>
<div class="message">
{{ 'Page:PleaseWait'|dict_s }}
</div>
</div>
</div>
</div>
{% endblock %}
{% block pPageLiveScripts %}
{{ parent() }}
<script type="text/javascript">
var sNodeCollapsedClass = 'glyphicon-menu-right';
var sNodeExpandedClass = 'glyphicon-menu-down';
var sNodeLoadingClass = 'glyphicon-refresh keep-spinning';
var iSearchDelay = 500
var sBrowseMode = '{{ sBrowseMode }}';
var oLevelsProperties = {{ aLevelsProperties|raw }};
var oRawDatas = {{ aItems|raw }};
var bIsFullyLoaded = ('{{ sDataLoading }}' === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}') ? true : false;
// Collapses / Expands all the tree nodes
var collapseAll = function()
{
$('#brick_content_tree .tree').toggle(false);
$('#brick_content_tree .tree-toggle .glyphicon').removeClass(sNodeExpandedClass+' '+sNodeLoadingClass).addClass(sNodeCollapsedClass);
};
var expandAll = function()
{
$('#brick_content_tree .tree').toggle(true);
$('#brick_content_tree .tree-toggle .glyphicon').removeClass(sNodeCollapsedClass+' '+sNodeLoadingClass).addClass(sNodeExpandedClass);
};
// Show a loader over the tree
var showTreeLoader = function()
{
$("#brick_content_tree").hide();
$('#brick_tree_overlay').show();
};
// Hide the loader over the tree
var hideTreeLoader = function()
{
$('#brick_tree_overlay').hide();
$("#brick_content_tree").show();
}
// Registers the toggle listeners on the tree nodes. Used after every AJAX calls.
var registerToggleListeners = function()
{
$('#brick_content_tree .tree-toggle').off('click').on('click', function (oEvent) {
oEvent.preventDefault();
// Forcing subitems to expand after a filter, so we can browse subitems of a filtered item. Else is the regular toggle
if($(this).parent().children('ul.tree:visible').length > 0 && $(this).parent().children('ul.tree:visible').children('li:visible').length === 0)
{
$(this).parent().children('ul.tree').children('li').toggle(true);
}else{
$(this).parent().children('ul.tree').toggle(200);
}
// Toggling glyphicon class
if($(this).find('.glyphicon').hasClass(sNodeCollapsedClass))
{
$(this).find('.glyphicon').removeClass(sNodeCollapsedClass+' '+sNodeLoadingClass).addClass(sNodeExpandedClass);
}
else
{
$(this).find('.glyphicon').removeClass(sNodeExpandedClass+' '+sNodeLoadingClass).addClass(sNodeCollapsedClass);
}
// Check if the node has no children, if so we try to load them through AJAX (Only for the current item)
if($(this).parent().children('ul.tree').children('li').length === 0)
{
$(this).find('.glyphicon').removeClass(sNodeCollapsedClass+' '+sNodeExpandedClass).addClass(sNodeLoadingClass);
loadChildNodes($(this).attr('data-level-alias'), $(this).attr('data-item-id'));
}
});
};
// Registers the filter listeners on the tree.
var registerFilterListeners = function()
{
$('#brick_search_field').treeListFilter('#brick_content_tree', iSearchDelay, filterResultsHandler);
};
var filterResultsHandler = function()
{
// If results shows intermediate levels without any leaves under, we show all its children.
$('#brick_content_tree .list-group-item:visible').each(function(iIndex, oElement){
if($(oElement).find('.list-group.tree:visible').length === 0)
{
$(oElement).find('.list-group.tree:not(:visible)').show();
$(oElement).find('.list-group.tree:not(:visible) .list-group-item').show();
}
else if($(oElement).find('.list-group.tree:visible .list-group-item:visible').length === 0)
{
$(oElement).find('.list-group.tree:visible .list-group-item:not(:visible)').show();
}
});
// Show / hide empty data message
if(bIsFullyLoaded)
{
hideTreeLoader();
if($('#brick_content_tree > .list-group-item:visible').length > 0)
{
$('#brick_content_empty').hide();
}
else
{
$('#brick_content_empty').show();
}
}
expandAll();
};
// Load current node childnodes throught AJAX
var loadChildNodes = function(sLevelAlias, sNodeId)
{
var sUrl = '{{ app.url_generator.generate('p_browse_brick_mode_tree', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseMode, 'sLevelAlias': '-sLevelAlias-', 'sNodeId': '-sNodeId-'})|raw }}';
sUrl = sUrl.replace(/-sLevelAlias-/, sLevelAlias).replace(/-sNodeId-/, sNodeId);
$.ajax(sUrl)
.done(function(data) {
$('#brick_content_tree .tree-toggle[data-level-alias="'+sLevelAlias+'"][data-item-id="'+sNodeId+'"] .glyphicon').removeClass(sNodeCollapsedClass+' '+sNodeLoadingClass).addClass(sNodeExpandedClass);
for(index in data.data)
{
var sublevel = data.data[index];
var sublevelData = {};
sublevelData[sublevel.level_alias+"::"+sublevel.id] = sublevel;
buildTree(sublevelData, sLevelAlias+"::"+sNodeId, false);
}
registerToggleListeners();
})
.fail(function() {
alert('{{ 'Error:XHR:Fail'|dict_s }}');
});
};
// Build tree nodes from data under the nodeId
var buildTree = function(data, nodeId, isRootLevel)
{
if(nodeId === undefined)
{
// We are on the root node
nodeId = 'L';
$('ul[data-level-id="'+nodeId+'"]').html('');
}
if(isRootLevel === undefined)
{
isRootLevel = true;
}
$.each(data, function(i, item){
var levelId = item.level_alias+'::'+item.id;
var levelAltId = item.level_alias+'_'+item.id;
var levelActions = oLevelsProperties[item.level_alias].actions;
var levelActionsKeys = Object.keys(levelActions);
var levelPrimaryAction = levelActions[levelActionsKeys[0]];
var url = '';
var liElem = $('<li></li>').addClass('list-group-item');
var aElem = $('<a></a>').addClass('tree-item').attr('data-item-id', item.id).attr('href', '#').attr('data-level-alias', item.level_alias).text(item.name);
// Building node
$('ul[data-level-id="'+nodeId+'"]').append(liElem);
liElem.append(aElem);
// Building tooltip for the node
if( (item.tooltip !== undefined) && (item.tooltip !== '') )
{
aElem.attr('title', item.tooltip).attr('data-toggle', 'tooltip').tooltip({html: true, trigger: 'hover', placement: 'right'});
}
// Building actions for that node
switch(levelPrimaryAction.type)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_DRILLDOWN') }}':
aElem.addClass('tree-toggle').html('<span class="glyphicon '+sNodeCollapsedClass+'" aria-hidden="true"></span><span class="list-group-item-text">'+aElem.text()+'</span>');
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = levelPrimaryAction.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
url = addParameterToUrl(url, 'ar_token', item.action_rules_token[levelPrimaryAction.type]);
aElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+levelPrimaryAction.type+'" not implemented for primary action');
break;
}
if(levelActionsKeys.length > 1)
{
var actionsElem = $('<div></div>').addClass('list-group-item-actions');
liElem.append(actionsElem);
// Preparing secondary actions for small screens
var actionsSSTogglerElem = $('<a class="glyphicon glyphicon-menu-hamburger visible-xs" data-toggle="collapse" data-target="#item-actions-menu-'+levelAltId+'"></a>');
var actionsSSMenuElem = $('<div id="item-actions-menu-'+levelAltId+'" class="item-action-wrapper panel panel-default"></div>');
var actionsSSMenuContainerElem = $('<div class="panel-body"></div>');
actionsSSMenuElem.append(actionsSSMenuContainerElem);
actionsElem.append(actionsSSTogglerElem);
actionsElem.append(actionsSSMenuElem);
var actionsButtons = {};
// Fill actionsButtons with all actions but the primary
for(j = 1; j < levelActionsKeys.length; j++)
{
actionsButtons[levelActionsKeys[j]] = levelActions[levelActionsKeys[j]];
}
for(j in actionsButtons)
{
var action = actionsButtons[j];
var actionElem = $('<a></a>');
switch(action.type)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url).text(action.title);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url).text(action.title);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\BrowseBrick::ENUM_ACTION_CREATE_FROM_THIS') }}':
url = action.url.replace(/-objectClass-/, item.class).replace(/-objectId-/, item.id);
url = addParameterToUrl(url, 'ar_token', item.action_rules_token[action.type]);
actionElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url).text(action.title);
break;
default:
console.log('Action "'+action.type+'" not implemented for secondary action');
break;
}
actionsSSMenuContainerElem.append( $('<p></p>').append(actionElem.clone()) );
actionsElem.append(actionElem.addClass('hidden-xs')); // We don't want to display it on small screens
}
}
// Building subnodes if necessary
var ulElem = $('<ul></ul>').addClass('list-group').addClass('tree').attr('data-level-id', levelId);
liElem.append(ulElem);
if(item.subitems.length !== 0)
{
buildTree(item.subitems, levelId, false);
}
});
// Update listeners
if(isRootLevel)
{
registerToggleListeners();
}
};
$(document).ready(function(){
// Init expand/collapse all buttons
$('#btn-collapse-all').on('click', function (oEvent) {
collapseAll();
});
$('#btn-expand-all').on('click', function (oEvent) {
if(!bIsFullyLoaded)
{
// Show a loader while fetching results
showTreeLoader();
// If we don't do that now, we have have several calls
bIsFullyLoaded = true;
// Display loader by toggling glyphicon class
$('#brick_content_tree .tree-toggle .glyphicon.'+sNodeCollapsedClass).removeClass(sNodeCollapsedClass).addClass(sNodeLoadingClass);
// Load the whole tree
$.ajax('{{ app.url_generator.generate('p_browse_brick_mode', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseMode, 'sDataLoading': constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL')})|raw }}')
.done(function(data)
{
buildTree(data.data);
})
.fail(function(){
bIsFullyLoaded = false;
})
.always(function(){
$('#brick_content_tree .tree-toggle .glyphicon').removeClass(sNodeCollapsedClass+' '+sNodeLoadingClass).addClass(sNodeExpandedClass);
// Hide loader no matter what
hideTreeLoader();
});
}
else
{
$('#brick_content_tree .tree-toggle .glyphicon').removeClass(sNodeCollapsedClass+' '+sNodeLoadingClass).addClass(sNodeExpandedClass);
}
expandAll();
});
// Init filter field
// Note : If placed in the registerFilterListeners function, must be before the .treeListFilter as the off('change') will remove .treeListFilter
$('#brick_search_field').on('change', function(oEvent){
// Show a loader while fetching/filtering results
showTreeLoader();
if(!bIsFullyLoaded)
{
// We don't want to trigger TreeListFilter yet
oEvent.stopPropagation();
// Load the whole tree
$.ajax('{{ app.url_generator.generate('p_browse_brick_mode', {'sBrickId': sBrickId, 'sBrowseMode': sBrowseMode, 'sDataLoading': constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL')})|raw }}')
.done(function(data)
{
bIsFullyLoaded = true;
// Updating tree
buildTree(data.data);
// Trigerring filter
$('#brick_search_field').trigger('change');
})
.fail(function(){
bIsFullyLoaded = false;
})
.always(function(){
// We don't need to call this because it will be called as a callback when "change" event is triggered on treeListFilter
//filterResultsHandler();
});
}
else
{
// // We don't need to call this because it will be called as a callback when "change" event is triggered on treeListFilter
filterResultsHandler();
}
});
// Auto collapse item actions popup
$('body').click(function(){
$('#brick_content_tree .item-action-wrapper.collapse.in').collapse('hide');
});
// Build the tree (collapsed)
showTreeLoader();
buildTree(oRawDatas);
hideTreeLoader();
registerFilterListeners();
collapseAll();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,29 @@
{# itop-portal-base/portal/src/views/bricks/layout.html.twig #}
{# Brick base layout #}
{% extends app['combodo.portal.instance.conf'].properties.templates.layout %}
{% block pPageTitle %}
{# Overloading the default template's title to show the brick's title #}
{% if oBrick is defined and oBrick is not null and oBrick.GetTitle() != '' %}
{{ oBrick.GetTitle()|dict_s }} - iTop
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
{% block pMainHeader %}
<div class="col-sm-6 col-md-8" id="main-header-title">
<h2>{% block pMainHeaderTitle %}{% endblock %}</h2>
</div>
<div class="col-sm-6 col-md-4" id="main-header-actions">
{% block pMainHeaderActions %}
{% endblock %}
</div>
{% endblock %}
{% block pMainContent %}
<div class="col-xs-12">
{% block pMainContentHolder%}
{% endblock %}
</div>
{% endblock %}

View File

@@ -0,0 +1,208 @@
{# itop-portal-base/portal/src/views/bricks/manage/layout.html.twig #}
{# Manage brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
{% block pMainHeaderTitle %}
{{ oBrick.GetTitle()|dict_s }}
{% endblock %}
{% block pMainHeaderActions %}
<div class="row">
<div class="col-sm-12">
{% if aGroupingTabsValues|length > 1 %}
<div class="btn-group btn-group-justified btn-group-sm">
{% for aGroupingTab in aGroupingTabsValues %}
<a href="{{ app.url_generator.generate('p_manage_brick', {'sBrickId': sBrickId, 'sGroupingTab': aGroupingTab.value}) }}" class="btn btn-default {% if sGroupingTab is defined and sGroupingTab == aGroupingTab.value %}active{% endif %}">{{ aGroupingTab.label|raw }}</a>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block pMainContentHolder%}
{% if aGroupingAreasData|length > 0 %}
{% for aAreaData in aGroupingAreasData %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ aAreaData.sTitle }}</h3>
</div>
<div class="panel-body">
{% if aAreaData.iItemsCount > 0 %}
<table id="table-{{ aAreaData.sId }}" class="table table-striped table-bordered responsive" width="100%"></table>
{% else %}
<div class="text-center">
{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% else %}
<div class="panel panel-default">
<div class="panel-body">
<h3 class="text-center">{{ 'Brick:Portal:Manage:Table:NoData'|dict_s }}</h3>
</div>
</div>
{% endif %}
{% endblock %}
{% block pPageLiveScripts %}
{{ parent() }}
<script type="text/javascript">
var sDataLoading = '{{ sDataLoading }}';
// Used for ajax throttling
var iSearchThrottle = 300;
var oKeyTimeout;
var aKeyTimeoutFilteredKeys = [16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40]; // Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
var columnsProperties = {
{% for aAreaData in aGroupingAreasData %}
'{{ aAreaData.sId }}': {{ aAreaData.aColumnsDefinition|json_encode()|raw }},
{% endfor %}
};
var rawData = {
{% for aAreaData in aGroupingAreasData %}
'{{ aAreaData.sId }}': {{ aAreaData.aItems|json_encode()|raw }},
{% endfor %}
};
// Columns definition for the table from the columnsProperties
var getColumnsDefinition = function(tableName)
{
var tableProperties = columnsProperties[tableName];
if(tableProperties === undefined)
{
console.log('Could not retrieve columns properties for table "'+tableName+'"');
return false;
}
if(rawData[tableName] === undefined)
{
console.log('Could not retrieve data for table "'+tableName+'"');
return false;
}
var columnsDefinition = [];
for(key in tableProperties)
{
columnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": (sDataLoading === '{{ constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') }}'),
"title": tableProperties[key].title,
"defaultContent": "",
"type": "html",
"data": "attributes."+key+".att_code",
"render": function(att_code, type, row){
var cellElem;
var itemActions;
var itemPrimarayAction;
// Preparing action on the cell
// Note : For now we will use only one action, the secondary actions are therefore not implemented. Only the data structure is done.
itemActions = row.attributes[att_code].actions;
// Preparing the cell data
cellElem = (itemActions.length > 0) ? $('<a></a>') : $('<span></span>');
cellElem.text(row.attributes[att_code].value);
// Building actions
if(itemActions.length > 0)
{
// - Primary action
itemPrimaryAction = itemActions[0];
switch(itemPrimaryAction.type)
{
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_VIEW') }}':
url = '{{ app.url_generator.generate('p_object_view', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
case '{{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::ENUM_ACTION_EDIT') }}':
url = '{{ app.url_generator.generate('p_object_edit', {'sObjectClass': '-objectClass-', 'sObjectId': '-objectId-'})|raw }}'.replace(/-objectClass-/, itemPrimaryAction.class).replace(/-objectId-/, itemPrimaryAction.id);
cellElem.attr('data-toggle', 'modal').attr('data-target', '#modal-for-all').attr('href', url);
break;
default:
console.log('Action "'+itemPrimaryAction+'" not implemented');
break;
}
// - Secondary actions
// Not done for now, only the data structure is ready in case we need it later
}
return cellElem.prop('outerHTML');
},
});
}
return columnsDefinition;
};
$(document).ready(function(){
{% for aAreaData in aGroupingAreasData %}
{% set sAreaId = aAreaData.sId %}
var oTable{{ sAreaId }} = $('#table-{{ sAreaId }}').DataTable( {
"language": {
"processing": "{{ 'Portal:Datatables:Language:Processing'|dict_s }}",
"search": "{{ 'Portal:Datatables:Language:Search'|dict_s }}",
"lengthMenu": "{{ 'Portal:Datatables:Language:LengthMenu'|dict_s }}",
"zeroRecords": "{{ 'Portal:Datatables:Language:ZeroRecords'|dict_s }}",
"info": "{{ 'Portal:Datatables:Language:Info'|dict_s }}",
"infoEmpty": "{{ 'Portal:Datatables:Language:InfoEmpty'|dict_s }}",
"infoFiltered": "({{ 'Portal:Datatables:Language:InfoFiltered'|dict_s }})",
"emptyTable": "{{ 'Portal:Datatables:Language:EmptyTable'|dict_s }}",
"paginate": {
"first": "{{ 'Portal:Datatables:Language:Paginate:First'|dict_s }}",
"previous": "{{ 'Portal:Datatables:Language:Paginate:Previous'|dict_s }}",
"next": "{{ 'Portal:Datatables:Language:Paginate:Next'|dict_s }}",
"last": "{{ 'Portal:Datatables:Language:Paginate:Last'|dict_s }}"
},
"aria": {
"sortAscending": ": {{ 'Portal:Datatables:Language:Sort:Ascending'|dict_s }}",
"sortDescending": ": {{ 'Portal:Datatables:Language:Sort:Descending'|dict_s }}"
}
},
"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]],
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Brick\\ManageBrick::DEFAULT_COUNT_PER_PAGE_LIST') }},
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"ri><"col-sm-6"p>>',
"columns": getColumnsDefinition('{{ sAreaId }}'),
"order": [[0, "desc"]],
{% if sDataLoading == constant('Combodo\\iTop\\Portal\\Brick\\AbstractBrick::ENUM_DATA_LOADING_FULL') %}
"data": rawData['{{ sAreaId }}'],
{% else %}
"processing": true,
"serverSide": true,
{#"searchDelay": 1000, // can be used to increase time between server calls when typing search query#}
"ajax": {
"url": "{{ app.url_generator.generate('p_manage_brick_lazy', {'sBrickId': sBrickId, 'sGroupingTab': sGroupingTab, 'sGroupingArea': sAreaId})|raw }}",
"data": function(d){
d.iPageNumber = Math.floor(d.start/d.length) + 1;
d.iCountPerPage = d.length;
d.columns = null;
d.orders = null;
if(d.search.value)
{
d.sSearchValue = d.search.value;
}
}
}
{% endif %}
} );
// Overrides filter input to apply throttle. Otherwise, an ajax request is send each time a key is pressed
// Also removes accents from search string
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
$('#table-{{ sAreaId }}_filter input').off().on('keyup', function(){
var me = this;
console.log('here');
clearTimeout(oKeyTimeout);
oKeyTimeout = setTimeout(function() {
oTable{{ sAreaId }}.search(me.value.latinise()).draw();
}, iSearchThrottle);
});
{% endfor %}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,19 @@
{# itop-portal-base/portal/src/views/bricks/object/layout.html.twig #}
{# Object brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
{% block pMainHeader %}
<div class="col-xs-12" id="main-header-title">
{% if form.title is defined %}
<h2>{{ form.title|raw }}</h2>
{% endif %}
</div>
{% endblock %}
{% block pMainContentHolder%}
<div class="panel panel-default">
<div class="panel-body">
{% include 'itop-portal-base/portal/src/views/bricks/object/mode_' ~ sMode ~ '.html.twig' %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{# itop-portal-base/portal/src/views/bricks/object/modal.html.twig #}
{# Object brick base layout #}
{% extends 'itop-portal-base/portal/src/views/modal/layout.html.twig' %}
{% block pModalTitle %}
{% if form.title is defined %}
{{ form.title|raw }}
{% endif %}
{% endblock %}
{% block pModalBody %}
{% include 'itop-portal-base/portal/src/views/bricks/object/mode_' ~ sMode ~ '.html.twig' with {tIsModal: true} %}
{% endblock %}

View File

@@ -0,0 +1,5 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_apply_stimulus.html.twig #}
{# Object brick apply stimulus layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig' %}
{# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #}

View File

@@ -0,0 +1,64 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig #}
{# Object brick create layout #}
{% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_form' %}
{% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
<form id="{{ sFormId }}" method="POST" action="{{ form.renderer.GetEndpoint()|raw }}">
<input type="hidden" name="transaction_id" value="{{ form.transaction_id }}" />
<div class="form_alerts">
{% block pFormAlerts %}
<div class="alert alert-success" role="alert" style="display: none;"></div>
<div class="alert alert-warning" role="alert" style="display: none;"></div>
<div class="alert alert-error alert-danger" role="alert" style="display: none;"></div>
{% endblock %}
</div>
<div class="form_fields">
{% block pFormFields %}
{{ form.renderer.GetBaseLayout()|raw }}
{% endblock %}
</div>
<div class="form_buttons">
{% block pFormButtons %}
{% if form.buttons is defined and form.buttons.transitions is defined and form.buttons.transitions|length > 0 %}
<div class="form_btn_transitions">
{% for sStimulusCode, sStimulusLabel in form.buttons.transitions %}
<button class="btn btn-default form_btn_transition" type="submit" name="stimulus_code" value="{{ sStimulusCode }}">{{ sStimulusLabel }}</button>
{% endfor %}
</div>
{% endif %}
<div class="form_btn_regular">
{% if form.editable_fields_count is defined and form.editable_fields_count > 0 %}
<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Cancel'|dict_s }}" data-dismiss="modal">
<input class="btn btn-primary form_btn_submit" type="submit" value="{{ 'Portal:Button:Submit'|dict_s }}">
{% else %}
{% if tIsModal %}
<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal">
{% endif %}
{% endif %}
</div>
{% endblock %}
</div>
</form>
<script type="text/javascript">
$(document).ready(function(){
var oFieldSet = $('#{{ sFormId }} > .form_fields').field_set({{ form.fieldset|json_encode()|raw }});
$('#{{ sFormId }}').portal_form_handler({
formmanager_class: "{{ form.formmanager_class|escape('js') }}",
formmanager_data: {{ form.formmanager_data|json_encode()|raw }},
field_set: oFieldSet,
submit_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_submit, .form_btn_transition'),
cancel_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_cancel'),
submit_url: {% if form.submit_callback is not null %}"{{ form.submit_callback }}"{% else %}null{% endif %},
cancel_url: {% if form.cancel_callback is not null %}"{{ form.cancel_callback }}"{% else %}null{% endif %},
endpoint: "{{ form.renderer.GetEndpoint()|raw }}",
is_modal: {% if tIsModal is defined and tIsModal == true %}true{% else %}false{% endif %}
});
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').find('.modal-footer').hide();
{% endif %}
});
</script>

View File

@@ -0,0 +1,5 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig #}
{# Object brick edit layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig' %}
{# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #}

View File

@@ -0,0 +1,59 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_search_hierarchy.html.twig #}
{# Object brick hierarchy search layout #}
{% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_search_form' %}
{% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
<div id="{{ sFormId }}">
{#<div class="form_alerts"></div>#}
<div class="form_fields">
<ul class="list-group" id="search-content-tree" data-level-id="L">
</ul>
</div>
<div class="form_buttons">
{% block pFormButtons %}
<div class="form_btn_regular">
<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Cancel'|dict_s }}" data-dismiss="modal">
<input class="btn btn-primary form_btn_submit" type="button" value="{{ 'Portal:Button:Submit'|dict_s }}">
</div>
{% endblock %}
</div>
</div>
<script type="text/javascript">
var oRawDatas = {{ aResults.aItems|raw }};
// Used for form
var oSelectedItem = {};
// Show a loader inside the table
var showTableLoader = function()
{
$('#search-content-table > tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
};
$(document).ready(function(){
showTableLoader();
// Handles submit button
$('#{{ sFormId }} .form_buttons .form_btn_submit').off('click').on('click', function(oEvent){
// Extracting value(s) to be send back to the source form
var oData = {value: {}};
var sItemId = Object.keys(oSelectedItem)[0];
var sItemName = oSelectedItem[sItemId];
oData.value[sItemId] = sItemName;
// Triggering value setting on the source field
$('[data-form-path="{{aSource.sFormPath}}"][data-field-id="{{aSource.sFieldId}}"]').triggerHandler('set_current_value', oData);
// Closing the modal
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').modal('hide');
{% endif %}
});
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').find('.modal-footer').hide();
{% endif %}
});
</script>

View File

@@ -0,0 +1,231 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_search_regular.html.twig #}
{# Object brick regular search layout #}
{% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_search_form' %}
{% set sTableId = 'search_content_table_' ~ sFormId %}
{% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
{% set bMultipleSelect = (bMultipleSelect is defined and bMultipleSelect == true) ? true : false %}
<div id="{{ sFormId }}">
{#<div class="form_alerts"></div>#}
<div class="form_fields">
<table id="{{ sTableId }}" class="table table-striped table-bordered responsive" cellspacing="0" width="100%">
<tbody>
</tbody>
</table>
</div>
<div class="form_buttons">
{% block pFormButtons %}
<div class="form_btn_regular">
<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Cancel'|dict_s }}" data-dismiss="modal">
<input class="btn btn-primary form_btn_submit" type="button" value="{{ 'Portal:Button:Submit'|dict_s }}">
</div>
{% endblock %}
</div>
</div>
<script type="text/javascript">
var oColumnProperties = {{ aColumnProperties|raw }};
var oRawDatas = {{ aResults.aItems|raw }};
var oTable;
// Used for ajax throttling
var iSearchThrottle = 600;
var oKeyTimeout;
var aKeyTimeoutFilteredKeys = [16, 17, 18, 19, 27, 33, 34, 35, 36, 37, 38, 39, 40]; // Shift, Ctrl, Alt, Pause, Esc, Page Up/Down, Home, End, Left/Up/Right/Down arrows
// Used for form
var oSelectedItems = {};
// Show a loader inside the table
var showTableLoader = function()
{
$('#{{ sTableId }} > tbody').html('<tr><td class="datatables_overlay" colspan="100">' + $('#page_overlay').html() + '</td></tr>');
};
// Columns definition for the table from the oLevelsProperties
var getColumnsDefinition = function()
{
var aColumnsDefinition = [];
var sFirstColumnId = Object.keys(oColumnProperties)[0];
for(sKey in oColumnProperties)
{
// Level main column
aColumnsDefinition.push({
"width": "auto",
"searchable": true,
"sortable": false,
"title": oColumnProperties[sKey].title,
"defaultContent": "",
"type": "html",
"data": "attributes."+sKey+".att_code",
"render": function(data, type, row){
var cellElem;
// Preparing the cell data
if(row.attributes[data].url !== undefined)
{
cellElem = $('<a></a>');
cellElem.attr('target', '_blank').attr('href', row.attributes[data].url);
}
else
{
cellElem = $('<span></span>');
}
cellElem.attr('data-object-id', row.id).html('<span>' + row.attributes[data].value + '</span>');
if(data === sFirstColumnId)
{
cellElem.prepend('<span class="row_input"><input type="{{ (bMultipleSelect) ? 'checkbox' : 'radio' }}" name="{{ sTargetAttCode }}" /></span>');
}
return cellElem.prop('outerHTML');
},
});
}
return aColumnsDefinition;
};
$(document).ready(function(){
showTableLoader();
// Note : Those options should be externalized in an library so we can use them on any DataTables for the portal.
// We would just have to override / complete the necessary elements
oTable = $('#{{ sTableId }}').DataTable({
"language": {
"processing": "{{ 'Portal:Datatables:Language:Processing'|dict_s }}",
"search": "{{ 'Portal:Datatables:Language:Search'|dict_s }}",
"lengthMenu": "{{ 'Portal:Datatables:Language:LengthMenu'|dict_s }}",
"zeroRecords": "{{ 'Portal:Datatables:Language:ZeroRecords'|dict_s }}",
"info": "{{ 'Portal:Datatables:Language:Info'|dict_s }}",
"infoEmpty": "{{ 'Portal:Datatables:Language:InfoEmpty'|dict_s }}",
"infoFiltered": "({{ 'Portal:Datatables:Language:InfoFiltered'|dict_s }})",
"emptyTable": "{{ 'Portal:Datatables:Language:EmptyTable'|dict_s }}",
"paginate": {
"first": "{{ 'Portal:Datatables:Language:Paginate:First'|dict_s }}",
"previous": "{{ 'Portal:Datatables:Language:Paginate:Previous'|dict_s }}",
"next": "{{ 'Portal:Datatables:Language:Paginate:Next'|dict_s }}",
"last": "{{ 'Portal:Datatables:Language:Paginate:Last'|dict_s }}"
},
"aria": {
"sortAscending": ": {{ 'Portal:Datatables:Language:Sort:Ascending'|dict_s }}",
"sortDescending": ": {{ 'Portal:Datatables:Language:Sort:Descending'|dict_s }}"
}
},
"lengthMenu": [[10, 20, 50, -1], [10, 20, 50, "{{ 'Portal:Datatables:Language:DisplayLength:All'|dict_s }}"]],
"displayLength": {{ constant('Combodo\\iTop\\Portal\\Controller\\ObjectController::DEFAULT_COUNT_PER_PAGE_LIST') }},
"dom": '<"row"<"col-sm-6"l><"col-sm-6"<f><"visible-xs"p>>>t<"row"<"col-sm-6"i><"col-sm-6"p>>',
"columns": getColumnsDefinition(),
"select": {
"style": "{{ (bMultipleSelect) ? 'multi' : 'os' }}"
},
"rowId": "id",
"rowCallback": function(oRow, oData){
// TODO : Remove this when it will be fixed in datatables;
// Note : This is an ugly hack from datatables.net to ensure that selected rows are marked selected when using server-side processing (pagination / search / sorting)
// See more here http://datatables.net/release-datatables/examples/server_side/select_rows.html
if(oData.id in oSelectedItems)
{
$(oRow).addClass('selected');
$(oRow).find('td:first-child input').prop('checked', true);
}
},
"processing": true,
"serverSide": true,
"ajax": {
"url": "{{ app.url_generator.generate('p_object_search_from_attribute', {'sTargetAttCode': sTargetAttCode, 'sHostObjectClass': sHostObjectClass, 'sHostObjectId': sHostObjectId})|raw }}",
"data": function(d){
d.iPageNumber = Math.floor(d.start/d.length) + 1;
d.iCountPerPage = d.length;
d.columns = null;
d.orders = null;
if(d.search.value)
{
d.sSearchValue = d.search.value;
}
}
}
});
// Handles items selection/deselection
oTable.off('select').on('select', function(oEvent, dt, type, indexes){
var aData = oTable.rows(indexes).data().toArray();
// Checking input
$('#{{ sTableId }} tr[role="row"].selected td:first-child input').prop('checked', true);
// Saving values in temp array
for(var i in aData)
{
var iItemId = aData[i].id;
if(!(iItemId in oSelectedItems))
{
oSelectedItems[iItemId] = aData[i].name;
}
}
});
oTable.off('deselect').on('deselect', function(oEvent, dt, type, indexes){
var aData = oTable.rows(indexes).data().toArray();
// Checking input
$('#{{ sTableId }} tr[role="row"]:not(.selected) td:first-child input').prop('checked', false);
// Saving values in temp array
for(var i in aData)
{
var iItemId = aData[i].id;
if(iItemId in oSelectedItems)
{
delete oSelectedItems[iItemId];
}
}
});
// Overrides filter input to apply throttle. Otherwise, an ajax request is send each time a key is pressed
// Also removes accents from search string
// Note : The '.off()' call is to unbind event from DataTables that where triggered before we could intercept anything
$('#{{ sTableId }}_filter input').off().on('keyup', function(event){
var me = this;
// We trigger the search only if those keys where not pressed
if(aKeyTimeoutFilteredKeys.indexOf(event.which) < 0)
{
clearTimeout(oKeyTimeout);
oKeyTimeout = setTimeout(function() {
oTable.search(me.value.latinise()).draw();
}, iSearchThrottle);
}
});
// Shows a loader in the table when processing
$('#{{ sTableId }}').on('processing.dt', function(event, settings, processing){
if(processing === true)
{
showTableLoader();
}
});
// Handles submit button
$('#{{ sFormId }} .form_buttons .form_btn_submit').off('click').on('click', function(oEvent){
// Extracting value(s) to be send back to the source form
{% if bMultipleSelect %}
var oData = {values: oSelectedItems};
{% else %}
var oData = {value: {}};
var sItemId = Object.keys(oSelectedItems)[0];
var sItemName = oSelectedItems[sItemId];
oData.value[sItemId] = sItemName;
{% endif %}
// Triggering value setting on the source field
$('[data-form-path="{{aSource.sFormPath}}"][data-field-id="{{aSource.sFieldId}}"]').triggerHandler('set_current_value', oData);
// Closing the modal
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').modal('hide');
{% endif %}
});
{% if tIsModal is defined and tIsModal == true %}
$('#{{ sFormId }}').closest('.modal').find('.modal-footer').hide();
{% endif %}
});
</script>

View File

@@ -0,0 +1,14 @@
{# itop-portal-base/portal/src/views/bricks/object/mode_view.html.twig #}
{# Object brick view layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig' %}
{# This layout is exactly the same as the mode_create.html.twig, we duplicated it in case we need to have some subtle differences #}
{% block pFormButtons %}
{% if tIsModal is defined and tIsModal == true %}
<div class="form_btn_regular">
<input class="btn btn-default form_btn_cancel" type="button" value="{{ 'Portal:Button:Close'|dict_s }}" data-dismiss="modal">
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,31 @@
{# itop-portal-base/portal/src/views/bricks/tile.html.twig #}
{# Base brick tile layout #}
{% if brick.GetId == 'create-user-request' %}
{% set sIcon = 'warning-sign-orange-100px.png' %}
{% elseif brick.GetId == 'ongoing-tickets-for-portal-user' %}
{% set sIcon = 'headset-mic-orange-100px.png' %}
{% elseif brick.GetId == 'closed-tickets-for-portal-user' %}
{% set sIcon = 'laptop-cursor-orange-100px.png' %}
{% elseif brick.GetId == 'services' %}
{% set sIcon = 'checklist-ok-orange-100px.png' %}
{% elseif brick.GetId == 'faq' %}
{% set sIcon = 'puzzle-piece-orange-100px.png' %}
{% else %}
{% set sIcon = '' %}
{% endif %}
<div class="col-xs-12 col-sm-{{ brick.GetWidth }}">
{% block pTileWrapper %}
<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}"
{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %}
{% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}
class="tile vertical-center" id="brick-{{ brick.GetId }}" data-brick-id="{{ brick.GetId }}">
<div class="tile_decoration"><img src="{{ app['combodo.portal.base.absolute_url'] }}img/icons/{{sIcon}}" /></div>
<div clss="tile_body">
<div class="tile_title">{{ brick.GetTitle|dict_s }}</div>
<div class="tile_description"></div>
</div>
</a>
{% endblock %}
</div>

View File

@@ -0,0 +1,84 @@
{# itop-portal-base/portal/src/views/bricks/browse/layout.html.twig #}
{# Browse brick base layout #}
{% extends 'itop-portal-base/portal/src/views/bricks/layout.html.twig' %}
{% block pMainHeaderTitle %}
{{ oBrick.GetTitle()|dict_s }}
{% endblock %}
{% block pMainContentHolder%}
<form class="">
<div class="row">
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:PersonalInformations:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Nom</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Lajarige" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Prénom</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Guillaume" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Email</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="guillaume.lajarige@combodo.com" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Téléphone</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="0625540067" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Organisation</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Combodo" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Site</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Siège" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Fonction</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Ingénieur R&D" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">Manager</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="John Doe" class="form-control" maxlength="255" /></div>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Photo</h3>
</div>
<div class="panel-body">
<div class="text-center">
<img src="{{ sUserPhotoUrl }}" style="max-width: 175px;"/>
<input type="file" id="xx" name="xx" />
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Class:appUserPreferences/Attribute:preferences'|dict_s }}</h3>
</div>
<div class="panel-body">
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">{{ 'UI:FavoriteLanguage'|dict_s }}</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="Français" class="form-control" maxlength="255" /></div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ 'Brick:Portal:UserProfile:Password:Title'|dict_s }}</h3>
</div>
<div class="panel-body">
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">{{ 'Brick:Portal:UserProfile:Password:ChoosePassword'|dict_s }}</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="" class="form-control" maxlength="255" /></div>
</div>
<div data-field-id="reason" data-form-path="aa" class="portal_form_field form_field">
<div class="form-group form_mandatory"><label for="field_reason_572c5888e6378" class="control-label">{{ 'Brick:Portal:UserProfile:Password:ConfirmPassword'|dict_s }}</label><input type="text" id="field_reason_572c5888e6378" name="reason" value="" class="form-control" maxlength="255" /></div>
</div>
</div>
</div>
</div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{# errors/layout.html.twig #}
{# Base error layout #}
{% extends 'itop-portal-base/portal/src/views/layout.html.twig' %}
{% block pNavigationWrapper %}
{% endblock %}
{% block pMainWrapper %}
<style>
.well {
margin: 50px auto;
text-align: center;
padding: 25px;
max-width: 600px;
}
h1, h2, h3, p {
margin: 0;
}
p {
font-size: 17px;
margin-top: 25px;
}
p a.btn {
margin: 0 5px;
}
h1 .ion {
vertical-align: -5%;
margin-right: 5px;
}
</style>
<div class="container">
<div class="well">
<h1><div class="ion ion-alert-circled"></div> {{ error_title }}</h1>
<p>{{ error_message }}</p>
<p>{{ 'Error:HTTP:GetHelp'|dict_s }}</p>
<p>
<a class="btn btn-default btn-lg" href="#" onclick="history.back(); return false;">{{ 'Page:GoPreviousPage'|dict_s }}</a>
<a class="btn btn-default btn-lg" href="{{ app.url_generator.generate('p_home') }}">{{ 'Page:GoPortalHome'|dict_s }}</a>
</p>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,44 @@
{# itop-portal-base-base/portal/src/views/home/layout.html.twig #}
{# Home layout #}
{% extends 'itop-portal-base/portal/src/views/layout.html.twig' %}
{% block pPageBodyClass %}home{% endblock %}
{# Showing only bricks that are not visible on the main content as well as a welcome message #}
{#{% block pNavigationSideMenu %}
<ul class="nav">
{% for brick in app['combodo.portal.instance.conf'].bricks %}
{% if brick.GetActive and brick.GetVisibleNavigationMenu and not brick.GetVisibleHome and brick.GetRouteName is not null %}
<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>
{{ brick.GetTitle|dict_s }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endblock %}#}
{% block pMainWrapper %}
{% set iCurrentTileIndex = 0 %}
<div class="container-fluid" id="main-wrapper">
<div class="row">
<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
{% for i in 1..(app['combodo.portal.instance.conf'].bricks_total_width / 12)|round(0, 'ceil') %}
<section class="row row-eq-height-sm tiles_wrapper" {% if loop.first %}id="top_tiles_row"{% endif %}>
{% set iCurrentRowWidth = 0 %}
{% for brick in app['combodo.portal.instance.conf'].bricks|slice(iCurrentTileIndex, app['combodo.portal.instance.conf'].bricks|length) if iCurrentRowWidth < 12 %}
{% if brick.GetVisibleHome %}
{% set iCurrentRowWidth = iCurrentRowWidth + brick.GetWidth %}
{% set iCurrentTileIndex = iCurrentTileIndex + 1 %}
{% if iCurrentRowWidth <= 12 %}
{% include '' ~ brick.GetTileTemplatePath with {brick: brick} %}
{% endif %}
{% endif %}
{% endfor %}
</section>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,319 @@
{# layout.html.twig #}
{# Base layout #}
{% if app['combodo.current_user'] is defined and app['combodo.current_user'] is not null %}
{% set bUserConnected = true %}
{% set sUserFullname = app['combodo.current_user'].Get('first_name') ~ ' ' ~ app['combodo.current_user'].Get('last_name') %}
{% set sUserEmail = app['combodo.current_user'].Get('email') %}
{% set sUserPhotoUrl = app['combodo.portal.base.absolute_url'] ~ 'img/user-profile-default-256px.png' %}
{% else %}
{% set bUserConnected = false %}
{% set sUserFullname = '' %}
{% set sUserEmail = '' %}
{% set sUserPhotoUrl = app['combodo.portal.base.absolute_url'] ~ 'img/user-profile-default-256px.png' %}
{% endif %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block pPageTitle %}{% if sPageTitle is defined and sPageTitle is not null %}{{ sPageTitle }} - iTop{% else %}{{ 'Page:DefaultTitle'|dict_s }}{% endif %}{% endblock %}</title>
<link rel="shortcut icon" href="{{ app['combodo.absolute_url'] }}images/favicon.ico?itopversion=$ITOP_VERSION$" />
{% block pPageStylesheets %}
{# First bootstrap core, lib themes, then bootstrap theme, portal adjustements #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
{# - Bootstrap Datetime picker #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
{# - Datatables #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/dataTables.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/fixedHeader.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/responsive.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/scroller.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/select.bootstrap.min.css" rel="stylesheet">
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/css/select.dataTables.min.css" rel="stylesheet">
{# - Misc libs #}
<link href="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/css/typeaheadjs.bootstrap.css" rel="stylesheet">
<link href="{{ app['combodo.absolute_url'] }}css/magnific-popup.css" rel="stylesheet">
{# - Bootstrap theme #}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.bootstrap }}" rel="stylesheet">
{# - Portal adjustments for BS theme #}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.portal }}" rel="stylesheet">
{# Custom CSS that is supposed to do adjustments to the portal #}
{% if app['combodo.portal.instance.conf'].properties.themes.custom is defined %}
<link href="{{ app['combodo.portal.instance.conf'].properties.themes.custom }}" rel="stylesheet">
{% endif %}
{# Others CSS that will come after the theme/portal/custom, in an undefined order #}
{% if app['combodo.portal.instance.conf'].properties.themes.others is defined %}
{% for theme in app['combodo.portal.instance.conf'].properties.themes.others %}
<link href="{{ theme }}" rel="stylesheet">
{% endfor %}
{% endif %}
{% endblock %}
{% block pPageScripts %}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery-ui-1.10.3.custom.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.magnific-popup.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.fileupload.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/latinise/latinise.min.js"></script>
{# Moment.js #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/moment/js/moment.min.js"></script>
{# Datatables #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.bootstrap.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.fixedHeader.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.responsive.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.scroller.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/dataTables.select.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/datatables/js/datetime-moment.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/dataTables.accentNeutraliseForFilter.js"></script>
{# CKEditor files for HTML WYSIWYG #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/ckeditor/adapters/jquery.js"></script>
{# Date-time picker for Bootstrap #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js"></script>
{# Typeahead files for autocomplete #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/bloodhound.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/typeahead.bundle.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/typeahead/js/typeahead.jquery.min.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/handlebars/js/handlebars.min-768ddbd.js"></script>
{# Form files #}
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/form_handler.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/form_field.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/subform_field.js"></script>
<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/field_set.js"></script>
{# Form files for portal #}
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_handler.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_field.js"></script>
<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}js/portal_form_field_html.js"></script>
{% endblock %}
</head>
<body class="{% block pPageBodyClass %}{% endblock %}">
{% block pPageBodyWrapper %}
{% block pNavigationWrapper %}
{# Topbar navigation menu for mobile screens #}
<nav class="navbar navbar-fixed-top navbar-inverse visible-xs" id="topbar" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ app.url_generator.generate('p_home') }}">iTop</a>
<p class="navbar-text">
<a class="navbar-link user_infos" href="{{ app.url_generator.generate('p_user_profile_brick') }}">
<span class="user_photo"><img src="{{ sUserPhotoUrl }}" alt="{{ sUserFullname }}" /></span>
<span class="user_fullname">{{ sUserFullname }}</span>
</a>
</p>
</div>
<div class="collapse navbar-collapse" id="navbar">
<ul class="nav navbar-nav">
{% block pNavigationTopBricks %}
<li>
<a href="{{ app.url_generator.generate('p_home') }}">
{{ 'Page:Home'|dict_s }}
</a>
</li>
{% for brick in app['combodo.portal.instance.conf'].bricks %}
{% if brick.GetActive and brick.GetVisibleNavigationMenu and brick.GetRouteName is not null %}
<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>
{{ brick.GetTitle|dict_s }}
</a>
</li>
{% endif %}
{% endfor %}
{% endblock %}
{% if bUserConnected %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="usermenu">
{{ sUserFullname }}
<span class="caret"></span>
</a>
<ul class="dropdown-menu" aria-labelledby="usermenu">
<li><a href="{{ app.url_generator.generate('p_user_profile_brick') }}"><span class="glyphicon glyphicon-user"></span>{{ 'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil'|dict_s }}</a></li>
{% for aPortal in app['combodo.portal.instance.conf'].portals %}
{% if aPortal.id != app['combodo.portal.instance.conf'].properties.id %}
{% set sGlyphiconClass = (aPortal.id == 'backoffice') ? 'glyphicon-list-alt' : 'glyphicon-new-window' %}
<li><a href="{{ aPortal.url }}" target="_blank"><span class="glyphicon {{ sGlyphiconClass }}"></span>{{ aPortal.label|dict_s }}</a></li>
{% endif %}
{% endfor %}
{# We display the separator only if the user has more then 1 portal. Meaning default portal + console or several portal at least #}
{% if app['combodo.portal.instance.conf'].portals|length > 1 %}
<li role="separator" class="divider"></li>
{% endif %}
<li><a href="{{ app['combodo.absolute_url'] }}pages/logoff.php"><span class="glyphicon glyphicon-log-out"></span>{{ 'Brick:Portal:UserProfile:Navigation:Dropdown:Logout'|dict_s }}</a></li>
</ul>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
{# Sidebar navigation menu for normal screens #}
<nav class="navbar-default hidden-xs col-sm-3 col-md-2" id="sidebar" role="navigation">
<div class="user_card">
<div class="user_photo">
<img src="{{ sUserPhotoUrl }}" alt="{{ sUserFullname }}" />
</div>
<div class="user_infos">
<div class="user_fullname">{{ sUserFullname }}</div>
<div class="user_email dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="user_options">
{{ sUserEmail }}
<span class="caret"></span>
</a>
<ul class="dropdown-menu user_options" aria-labelledby="user_options">
<li><a href="{{ app.url_generator.generate('p_user_profile_brick') }}"><span class="glyphicon glyphicon-user"></span>{{ 'Brick:Portal:UserProfile:Navigation:Dropdown:MyProfil'|dict_s }}</a></li>
{% for aPortal in app['combodo.portal.instance.conf'].portals %}
{% if aPortal.id != app['combodo.portal.instance.conf'].properties.id %}
{% set sGlyphiconClass = (aPortal.id == 'backoffice') ? 'glyphicon-list-alt' : 'glyphicon-new-window' %}
<li><a href="{{ aPortal.url }}" target="_blank"><span class="glyphicon {{ sGlyphiconClass }}"></span>{{ aPortal.label|dict_s }}</a></li>
{% endif %}
{% endfor %}
{# We display the separator only if the user has more then 1 portal. Meaning default portal + console or several portal at least #}
{% if app['combodo.portal.instance.conf'].portals|length > 1 %}
<li role="separator" class="divider"></li>
{% endif %}
<li><a href="{{ app['combodo.absolute_url'] }}pages/logoff.php"><span class="glyphicon glyphicon-log-out"></span>{{ 'Brick:Portal:UserProfile:Navigation:Dropdown:Logout'|dict_s }}</a></li>
</ul>
</div>
</div>
</div>
<div class="menu">
{% block pNavigationSideMenu %}
<ul class="nav">
<li>
<a href="{{ app.url_generator.generate('p_home') }}">
{{ 'Page:Home'|dict_s }}
</a>
</li>
{% for brick in app['combodo.portal.instance.conf'].bricks %}
{% if brick.GetActive and brick.GetVisibleNavigationMenu and brick.GetRouteName is not null %}
<li class="{% if oBrick is defined and brick.id == oBrick.id %}active{% endif %}">
<a href="{{ app.url_generator.generate(brick.GetRouteName, {sBrickId: brick.GetId}) }}{% if app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] is defined %}#{{ app['combodo.portal.instance.routes'][brick.GetRouteName]['hash'] }}{% endif %}" {% if app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] is defined %}{% for key, value in app['combodo.portal.instance.routes'][brick.GetRouteName]['navigation_menu_attr'] %} {{ key }}="{{ value }}"{% endfor %}{% endif %} {% if brick.GetModal %}data-toggle="modal" data-target="#modal-for-all"{% endif %}>
{{ brick.GetTitle|dict_s }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endblock %}
</div>
{% if app['combodo.portal.instance.conf'].properties.logo is not null %}
<div class="logo">
{# This is a debug helper to know in which screen size we are #}
{% if app['debug'] %}
<div>Debug : Taille <span class="hidden-sm hidden-md hidden-lg">XS</span><span class="hidden-xs hidden-md hidden-lg">SM</span><span class="hidden-xs hidden-sm hidden-lg">MD</span><span class="hidden-xs hidden-sm hidden-md">LG</span></div>
{% endif %}
<a href="{{ app.url_generator.generate('p_home') }}" title="{{ app['combodo.portal.instance.conf'].properties.name|dict_s }}">
<img src="{{ app['combodo.portal.instance.conf'].properties.logo }}" alt="{{ app['combodo.portal.instance.conf'].properties.name|dict_s }}" />
</a>
</div>
{% endif %}
</nav>
{% endblock %}
{% block pMainWrapper %}
<div class="container-fluid" id="main-wrapper">
<div class="row">
<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
<section class="row row-eq-height-sm" id="main-header">
{% block pMainHeader %}
{% endblock %}
</section>
<section class="row row-eq-height-sm" id="main-content">
{% block pMainContent %}
{% endblock %}
</section>
</div>
</div>
</div>
{% endblock %}
<footer id="footer-wrapper">
{% block pPageFooter%}
<a href="http://www.combodo.com">iTop &copy; {{ "now"|date('Y') }}</a>
{% endblock %}
</footer>
<div class="modal fade" id="modal-for-all" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="content_loader">
<div class="icon glyphicon glyphicon-refresh"></div>
<div class="message">
{{ 'Page:PleaseWait'|dict_s }}
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modal-for-alert" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="{{ 'Portal:Button:Close'|dict_s }}"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"></h4>
</div>
<div class="modal-body">
<div class="alert">
</div>
<div class="text-right">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Portal:Button:Close'|dict_s }}</button>
</div>
</div>
</div>
</div>
</div>
<div id="page_overlay" class="global_overlay">
<div class="overlay_content">
<div class="content_loader">
<div class="icon glyphicon glyphicon-refresh"></div>
<div class="message">
{{ 'Page:PleaseWait'|dict_s }}
</div>
</div>
</div>
</div>
{% endblock %}
{% block pPageLiveScripts %}
<script type="text/javascript">
var addParameterToUrl = function(sUrl, sParamName, sParamValue)
{
sUrl += (sUrl.split('?')[1] ? '&':'?') + sParamName + '=' + sParamValue;
return sUrl;
};
$(document).ready(function(){
{% block pPageReadyScripts %}
// Hack to enable a same modal to load content from different urls
$('body').on('hidden.bs.modal', '.modal#modal-for-all', function () {
$(this).removeData('bs.modal');
$(this).find('.modal-content').html('<div class="content_loader"><div class="icon glyphicon glyphicon-refresh"></div><div class="message">{{ 'Page:PleaseWait'|dict_s }}</div></div>');
});
// Hack to enable multiple modals by making sure the .modal-open class is set to the <body> when there is at least one modal open left
$('body').on('hidden.bs.modal', function () {
if($('.modal.in').length > 0)
{
$('body').addClass('modal-open');
}
});
// Hide tooltips when a modal is opening, otherwise it might be overlapping it
$('body').on('show.bs.modal', function (event) {
$(this).find('[data-toggle*="tooltip"]').tooltip('hide');
});
{% endblock %}
});
</script>
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,12 @@
{# modal/layout.html.twig #}
{# Base modal layout, used to fill Bootstrap .modal-content #}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">{% block pModalTitle %}{% endblock %}</h4>
</div>
<div class="modal-body">{% block pModalBody %}{% endblock %}</div>
<div class="modal-footer">
{% block pModalFooter %}
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'Portal:ButtonClose'|dict_s }}</button>
{% endblock %}
</div>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,784 @@
/*******************/
/* Global settings */
/*******************/
@media (max-width: 768px){
body{
padding-top: 60px;
}
}
footer{
margin: 5em 1em;
}
/* Navigation menu */
.navbar-nav .dropdown-menu a .glyphicon,
.user_infos .dropdown-menu a .glyphicon{
margin-right: 15px;
}
/* Topbar */
#topbar #navbar{
overflow-y: auto;
}
#topbar .user_infos{
text-decoration: none;
}
#topbar .user_photo{
margin-right: 10px;
}
#topbar .user_photo img{
margin-top: -4px;
max-width: 100%;
max-height: 100%;
height: 25px;
border-radius: 100%;
}
/* Sidebar */
#sidebar{
position: fixed;
top: 0px;
left: 0px;
padding: 0px; /* Overriding BS */
height: 100%;
}
#sidebar .user_card{
padding: 30px 0px;
background-color: #F2F2F2; /* TODO : Change this */
text-align: center;
}
#sidebar .user_card .user_photo{
margin-bottom: 10px;
}
#sidebar .user_card .user_photo img{
border-radius: 100%;
width: 70px;
}
#sidebar .user_card .user_infos{
font-size: 1em;
}
#sidebar .user_card .user_options.dropdown-menu{
width: 92%;
left: 4%;
}
#sidebar .user_card .user_fullname{
font-weight: 600;
}
#sidebar .menu{
max-height: 59%;
overflow-y: auto;
}
#sidebar .logo{
position: absolute;
bottom: 15px;
width: 100%;
text-align: center;
}
/* Overlays*/
.global_overlay{
z-index: 9999;
display: none;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.5;
}
#page_overlay .overlay_content{
margin-top: 20em;
width: 100%;
color: white;
}
.overlay_content{
text-align: center;
}
.content_loader .icon{
margin-bottom: 0.3em;
/*width: 52px;*/
height: 38px; /* 50px; *//* Hack to make loader circle perfectly */
font-size: 3em; /* 4em; */
animation: spin 1.2s linear infinite;
-webkit-animation: spin 1.2s linear infinite;
-moz-animation: spin 1.2s linear infinite;
-ms-animation: spin 1.2s linear infinite;
}
.content_loader .message{
font-size: 1.5em; /* 2em; */
}
.datatables_overlay{
padding: 5% 0px !important;
background-color: white;
}
/******************/
/* Global classes */
/******************/
@media (min-width: 768px) {
.row-eq-height-sm {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
@media (min-width: 992px) {
.row-eq-height-md {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
@media (min-width: 1200px) {
.row-eq-height-lg {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
.vertical-center {
/* Make it a flex container */
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
/* Align the bootstrap's container vertically */
-webkit-box-align : center;
-webkit-align-items : center;
-moz-box-align : center;
-ms-flex-align : center;
align-items : center;
/* Also 'margin: 0 auto' doesn't have any effect on flex items in such web browsers
hence the bootstrap's container won't be aligned to the center anymore.
Therefore, we should use the following declarations to get it centered again */
-webkit-box-pack : center;
-moz-box-pack : center;
-ms-flex-pack : center;
-webkit-justify-content : center;
justify-content : center;
}
/*********************/
/* Global animations */
/*********************/
/* Spin */
@keyframes spin{
100% {
transform: rotate(360deg);
}
}
@-webkit-keyframes spin{
100% {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes spin{
100% {
-moz-transform: rotate(360deg);
}
}
@-ms-keyframes spin{
100% {
-ms-transform: rotate(360deg);
}
}
/***************/
/* BS override */
/***************/
@font-face {
font-family: 'Glyphicons Halflings';
src: url('../lib/bootstrap/fonts/glyphicons-halflings-regular.eot');
src: url('../lib/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../lib/bootstrap/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../lib/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../lib/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../lib/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
label{
font-weight: bold;
}
/*********************/
/* BS theme override */
/*********************/
.pagination{
margin: 14px 0px;
}
.list-group.tree{
margin-top: 11px;
margin-bottom: -11px;
}
.list-group-item{
padding-right: 0px; /* To align all actions on the right without indent */
}
.tooltip-inner{
max-width: 400px;
padding: 15px;
}
.nav > li.active > a{
font-weight: 600;
}
/* Custom "glyphicons" */
.glyphicon-ext-hierarchy:before {
content: url('../img/icons/hierarchy-white-13px.png');
}
/**********************/
/* BS plugin override */
/**********************/
/* Datatables */
.table-striped > tbody > tr:hover {
background-color: #fdf5d0;
}
/******************/
/* Modal settings */
/******************/
.modal-content .content_loader{
margin: 7em 0em;
text-align: center;
}
@media (min-width: 992px){
.modal-lg {
width: 80%;
max-width: 1200px;
}
}
/**************************/
/* MagnificPopup settings */
/**************************/
.mfp-bg{
z-index: 1200;
}
.mfp-wrap{
z-index: 1210;
}
/********************/
/* Typeahed setting */
/********************/
.twitter-typeahead .tt-menu{
max-height: 200px;
overflow-y: auto;
}
@media (min-width: 768px){
.twitter-typeahead .tt-menu{
max-height: 300px;
}
}
.twitter-typeahead .tt-dataset > .content_loader{
margin: 10px 0px;
text-align: center;
font-size: 0.6em;
}
.twitter-typeahead .tt-dataset > .content_loader .icon{
height: 25px;
}
.twitter-typeahead .tt-dataset .no_result{
text-align: center;
font-style: italic;
}
/*****************/
/* Home settings */
/*****************/
.home #main-wrapper{
padding-top: 15px;
}
@media (min-width: 768px) {
.home .tiles_wrapper{
/*margin-bottom: 18px;*/
margin-bottom: 35px;
}
}
.home .tile{
margin-bottom: 8px;
min-height: 4em;
background-color: #FFFFFF;
background-image: none;
border: 1px solid #8A8A8A;
border-radius: 0px;
text-align: center;
text-decoration: none;
white-space: normal;
}
.home .tile .tile_decoration{
position: absolute;
top: 3px;
left: 21px;
}
.home .tile .tile_decoration > img{
width: 45px;
max-height: 45px;
}
.home .tile .tile_title{
font-weight: bold;
color: #333;
}
.home .tile .tile_description{
display: none;
color: #555555;
}
@media (min-width: 768px) {
.home .tile{
margin-bottom: 0px;
min-height: 10em;
}
.home .tile .tile_decoration{
position: absolute;
top: -30px;
left: 0px;
width: 100%;
}
.home .tile .tile_decoration > img{
width: 55px;
max-height: 55px;
}
.home .tile .tile_title{
font-size: 1.0em;
}
.home .tile .tile_description{
display: block;
margin: 15px 20px 0px 20px;
text-align: justify;
}
}
@media (min-width: 992px) {
.home .tile{
min-height: 15em;
}
.home .tile .tile_decoration{
top: -35px;
}
.home .tile .tile_decoration > img{
width: 85px;
max-height: 85px;
}
.home .tile .tile_title{
font-size: 1.4em;
}
}
/********************/
/* Modules settings */
/********************/
#main-header-title{
text-align: center;
}
@media (min-width: 768px) {
#main-header-title{
min-height: 6em;
text-align: left;
}
}
#main-header-actions > .row{
margin-top: 20px;
}
@media(max-width: 768px){
#main-header-actions{
margin-bottom: 20px;
}
}
.dataTables_wrapper{
padding: 10px 10px;
}
#brick_content_toolbar{
margin: 10px 0px 6px 0px;
}
#brick_content_toolbar > div label{
font-weight: normal;
white-space: nowrap;
text-align: left;
}
#brick_content_toolbar > div label input{
margin-left: 0.5em;
display: inline-block;
width: 130px;
}
/***********************/
/* Brick communication */
/***********************/
/* Home tile */
.home .tile.tile_communication{
padding: 20px;
background-color: #EDEDED;
border: none;
font-weight: initial;
}
.home .tile_communication .carousel{
margin-bottom: 0px;
width: 100%;
height: 200px;
}
/**********************/
/* Brick user profile */
/**********************/
.home .userprofile-brick{
background-color: #E8E7E7;
}
/****************/
/* Brick browse */
/****************/
/* - Tree mode */
/****************/
#brick_content_tree{
position: relative;
}
.panel > .list-group:last-child .list-group-item:last-child,
.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child{
margin-bottom: 1px;
}
.list-group-item > .list-group-item-actions{
/*display: none; Displaying actions only when hovering was not unanimous in the team */
position: absolute;
top: 10px;
right: 10px;
}
.list-group-item:hover > .list-group-item-actions{
display: block;
}
.list-group-item .list-group-item-actions a:not(:first-child){
margin-left: 10px;
}
.list-group-item .list-group-item-text{
margin-left: 5px;
font-size: 1em;
line-height: 1em;
}
.list-group-item .keep-spinning{
-webkit-animation: spin 1s linear infinite;
}
/* Secondary actions */
table .group-actions{
position: relative;
}
.list-group-item-actions a.glyphicon-menu-hamburger,
table .group-actions a.glyphicon-menu-hamburger{
cursor: pointer;
text-decoration: none;
}
.list-group-item-actions .item-action-wrapper,
table .group-actions .item-action-wrapper
{
display: none;
position: absolute;
z-index: 5;
bottom: 5px;
right: 0px;
-webkit-box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
-moz-box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);
}
.list-group-item-actions .item-action-wrapper.collapse.in,
table .group-actions .item-action-wrapper.collapse.in{
display: block;
}
.list-group-item-actions .item-action-wrapper .panel-body > p,
table .group-actions .item-action-wrapper .panel-body > p{
white-space: nowrap;
}
.list-group-item-actions .item-action-wrapper .panel-body > p:last-child,
table .group-actions .item-action-wrapper .panel-body > p:last-child{
margin-bottom: 0px;
}
#brick_content_empty{
display: none;
padding: 40px;
font-size: 1.3em;
font-style: italic;
}
/* Loader */
#brick_tree_overlay{
/*z-index: 10;*/
display: none;
padding: 8% 0px;
/*position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
min-height: 130px;*/
/*background-color: black;*/
border-radius: 0px 0px 4px 4px;
/*opacity: 0.5;
color: white;*/
font-size: 1em;
}
/****************/
/* - List mode */
/****************/
/*********/
/* Forms */
/*********/
.form_fields textarea{
height: 160px;
}
.form_field .form_mandatory .control-label:after{
content: "\002a";
position: relative;
left: 3px;
color: red; /* TODO : SASS this */
font-size: 0.9em;
}
/* Subform field */
.subform_field > fieldset{
margin: inherit;
margin-bottom: 15px; /* TODO : SASS this from .form-group */
padding: 10px 15px;
border: 1px solid #dddddd; /* TODO : SASS this */
border-radius: 4px; /* TODO : SASS this */
}
.subform_field > fieldset > legend{
margin: 0px 0px;
padding: 0px 7px;
width: inherit;
border: none;
font-size: 1em;
font-weight: bold;
color: #777777; /* TODO : SASS this */
}
/* CaseLog field */
.caselog_field_entry{
border: 1px solid #dddddd;
border-top: none;
}
.caselog_field_entry_header{
padding: 6px;
font-size: 1em;
border-bottom: 1px solid #FFFFFF;
background-color: #F2F2F2;
}
.caselog_field_entry_button{
display: block;
width: 15px;
height: 15px;
text-align: center;
line-height: 15px;
font-size: 16px;
border: 1px solid #a6a6a6;
border-bottom-color: #979797;
}
.caselog_field_entry_button:hover{
background-color: #cccccc;
}
.caselog_field_entry_button:before{
content: "▴";
}
.caselog_field_entry_button.collapsed:before{
content: "▾";
}
.caselog_field_entry_content{
margin: 10px;
overflow-x: auto;
}
/* FileUpload */
.fileupload_field_content{
padding: 8px 23px;
border: 1px solid #DDDDDD; /* TODO : SASS this */
background-color: #F9F9F9; /* TODO : SASS this*/
}
.fileupload_field_content > div{
margin-bottom: 15px;
}
.attachments_container .attachment {
height: 95px;
overflow-x: hidden;
text-align: center;
}
.attachments_container .attachment:hover {
background-color: #e0e0e0;
}
.attachments_container .attachment .attachment_name{
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.attachments_container .attachment .btn{
margin-top: 3px;
}
.upload_container input{
display: inline;
}
.upload_container .loader{
visibility: hidden;
margin-left: 7px;
font-size: 1.2em;
animation: spin 1.0s linear infinite;
-webkit-animation: spin 1.0s linear infinite;
-moz-animation: spin 1.0s linear infinite;
-ms-animation: spin 1.0s linear infinite;
}
#drag_overlay{
display: block;
top: inherit;
bottom: 0px;
height: 0px;
}
#drag_overlay.drag_in{
animation: show-drop-zone 0.3s ease-out forwards;
}
#drag_overlay.drag_out{
animation: hide-drop-zone 0.3s ease-out forwards;
}
#drag_overlay .overlay_content{
margin-top: 5em;
width: 100%;
color: white;
}
#drag_overlay .overlay_content .icon{
font-size: 3em;
}
#drag_overlay .overlay_content .message{
font-size: 1.5em;
}
@keyframes show-drop-zone{
100% {
height: 20%;
}
}
@-webkit-keyframes show-drop-zone{
100% {
height: 20%;
}
}
@-moz-keyframes show-drop-zone{
100% {
height: 20%;
}
}
@-ms-keyframes show-drop-zone{
100% {
height: 20%;
}
}
@keyframes hide-drop-zone{
0% {
height: 20%;
}
100% {
height: 0%;
}
}
@-webkit-keyframes hide-drop-zone{
0% {
height: 20%;
}
100% {
height: 0%;
}
}
@-moz-keyframes hide-drop-zone{
0% {
height: 20%;
}
100% {
height: 0%;
}
}
@-ms-keyframes hide-drop-zone{
0% {
height: 20%;
}
100% {
height: 0%;
}
}
.form_field .form-control-static img{
max-width: 100% !important;
height: initial !important;
}
.form_buttons{
padding-top: 20px;
text-align: center;
}
.form_buttons .form_btn_transitions{
margin-bottom: 20px;
}
@media (min-width: 768px){
.form_buttons .form_btn_transitions{
float: left !important;
}
.form_buttons .form_btn_regular{
text-align: right;
}
.form_buttons .form_btn_regular btn{
width: inherit;
}
}
/* CKEditor : Adding BS error feedback */
.form_field div.cke{
border: 1px solid #dddddd; /* TODO : SASS this */
}
.form_field.has-error div.cke{
border: 1px solid #D9230F; /* TODO : SASS this */
border-radius: 3px; /* TODO : SASS this */
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}
/* CKEditor : Styling notifications based on BS alerts */
.cke_notification{
position: relative;
padding: 15px;
margin-bottom: 18px;
border: 1px solid transparent;
border-radius: 4px;
background-color: #FFFFFF
}
.cke_notification_close{
position: absolute;
top: 2px;
right: 5px;
}
.cke_notification_message{
margin-bottom: 0px;
line-height: 1em;
font-size: 1em;
}
.cke_notification_success{ /* TODO : SASS this */
display: none;
background-color: #dff0d8;
border-color: #d6e9c6;
color: #468847;
}
.cke_notification_warning{ /* TODO : SASS this */
background-color: #fcf8e3;
border-color: #fbeed5;
color: #c09853;
}
/* DataTables : Fit the table in the form */
.form_linkedset_wrapper .dataTables_wrapper{
margin-bottom: 5px;
padding: 0px;
}
/* DataTables : Selection inputs */
.dataTable.table td span.row_input{
display: inline-block;
margin-right: 5px;
vertical-align: middle;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,92 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Required constants :
* - PORTAL_MODULE_ID : Name of the portal instance module
* - PORTAL_ID : Name of the portal instance module design (Configuration)
*/
// Silex framework and components
require_once APPROOT . '/lib/silex/vendor/autoload.php';
// iTop application requirements
//require_once __DIR__.'/../../../../approot.inc.php'; // Required by the instanciation module
//require_once APPROOT.'/application/startup.inc.php'; // Required by the instanciation module
require_once APPROOT . '/core/moduledesign.class.inc.php';
require_once APPROOT . '/application/loginwebpage.class.inc.php';
require_once APPROOT . '/sources/autoload.php';
// Portal
require_once __DIR__ . '/../src/providers/urlgeneratorserviceprovider.class.inc.php';
require_once __DIR__ . '/../src/helpers/urlgeneratorhelper.class.inc.php';
require_once __DIR__ . '/../src/providers/contextmanipulatorserviceprovider.class.inc.php';
require_once __DIR__ . '/../src/helpers/contextmanipulatorhelper.class.inc.php';
require_once __DIR__ . '/../src/providers/scopevalidatorserviceprovider.class.inc.php';
require_once __DIR__ . '/../src/helpers/scopevalidatorhelper.class.inc.php';
require_once __DIR__ . '/../src/helpers/securityhelper.class.inc.php';
require_once __DIR__ . '/../src/helpers/applicationhelper.class.inc.php';
// Forms
require_once __DIR__ . '/../src/forms/objectformmanager.class.inc.php';
use \Exception;
use \Symfony\Component\HttpFoundation\Response;
use \Combodo\iTop\Portal\Helper\ApplicationHelper;
// Checking user rights and prompt if needed
LoginWebPage::DoLoginEx(PORTAL_ID);
// Initializing Silex framework
$oApp = new Silex\Application();
// Registring optional silex components
$oApp->register(new Combodo\iTop\Portal\Provider\UrlGeneratorServiceProvider());
$oApp->register(new Combodo\iTop\Portal\Provider\ContextManipulatorServiceProvider());
$oApp->register(new Combodo\iTop\Portal\Provider\ScopeValidatorServiceProvider(), array(
'scope_validator.scopes_path' => utils::GetCachePath(),
'scope_validator.scopes_filename' => PORTAL_ID . '.scopes.php',
'scope_validator.instance_name' => PORTAL_ID
));
$oApp->register(new Silex\Provider\TwigServiceProvider(), array(
'twig.path' => APPROOT . 'env-' . utils::GetCurrentEnvironment()
));
// Configuring Silex application
$oApp['debug'] = false;
$oApp['combodo.absolute_url'] = utils::GetAbsoluteUrlAppRoot();
$oApp['combodo.portal.base.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/';
$oApp['combodo.portal.instance.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_MODULE_ID . '/';
$oApp['combodo.portal.instance.id'] = PORTAL_MODULE_ID;
$oApp['combodo.portal.instance.conf'] = array();
$oApp['combodo.portal.instance.routes'] = array();
// Registering error/exception handler in order to transform php error to exception
ApplicationHelper::RegisterExceptionHandler($oApp);
// Preparing portal foundations (Can't use Silex autoload through composer as we don't follow PSR conventions -filenames, functions-)
ApplicationHelper::LoadControllers();
ApplicationHelper::LoadRouters();
ApplicationHelper::RegisterRoutes($oApp);
ApplicationHelper::LoadBricks();
ApplicationHelper::RegisterTwigExtensions($oApp);
// Loading portal configuration from the module design
ApplicationHelper::LoadPortalConfiguration($oApp);
// Loading current user
ApplicationHelper::LoadCurrentUser($oApp);
// Running application
$oApp->run();

View File

@@ -0,0 +1,13 @@
/*
* This NEEDS the latinise/latinise.min.js to work.
*
* Sets a particular search function on the DataTables to neutralise accents while filtering
* Works only for string|html type columns
*/
$.fn.DataTable.ext.type.search.html = function(data){
return (!data) ? '' : ( (typeof data === 'string') ? data.latinise() : data );
};
$.fn.DataTable.ext.type.search.string = function(data){
return (!data) ? '' : ( (typeof data === 'string') ? data.latinise() : data );
};

View File

@@ -0,0 +1,59 @@
//iTop Portal Form field
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'portal_form_field' the widget name
$.widget( 'itop.portal_form_field', $.itop.form_field,
{
// default options
options:
{
on_validation_callback: function(me, oResult){
me.element.removeClass('has-success has-warning has-error')
me.element.find('.help-block').html('');
if(!oResult.is_valid)
{
me.element.addClass('has-error');
for(var i in oResult.error_messages)
{
me.element.find('.help-block').append($('<p>' + oResult.error_messages[i] + '</p>'));
}
}
}
},
// the constructor
_create: function()
{
this.element
.addClass('portal_form_field');
this._super();
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('portal_form_field');
this._super();
},
// _setOptions is called with a hash of all options that are changing
// always refresh when changing options
_setOptions: function()
{
this._superApply(arguments);
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._super( key, value );
},
showOptions: function()
{
return this.options;
}
});
});

View File

@@ -0,0 +1,84 @@
//iTop Portal Form field HTML
//Used for field containing html data such as rich editors, html blocks, ...
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'portal_form_field' the widget name
$.widget( 'itop.portal_form_field_html', $.itop.portal_form_field,
{
// the constructor
_create: function()
{
this.element
.addClass('portal_form_field_html');
this._super();
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('portal_form_field_html');
this._super();
},
// _setOptions is called with a hash of all options that are changing
// always refresh when changing options
_setOptions: function()
{
this._superApply(arguments);
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._super( key, value );
},
validate: function(oEvent, oData)
{
var oResult = { is_valid: true, error_messages: [] };
// Doing data validation
if(this.options.validators !== null)
{
var bMandatory = (this.options.validators.mandatory !== undefined);
// Extracting value for the field (without the tags)
// Note : The following code comes from /js/forms-json-utils.js / ValidateCKEditField()
var sTextContent = '';
var oFormattedContents = this.element.find('.cke iframe');
if (oFormattedContents.length == 0)
{
var oSourceContents = this.element.find('.cke textarea.cke_source');
sTextContent = oSourceContents.val();
}
else
{
sTextContent = oFormattedContents.contents().find("body").text();
if (sTextContent == '')
{
// No plain text, maybe there is just an image...
var oImg = oFormattedContents.contents().find('body img');
if (oImg.length != 0)
{
sTextContent = 'image';
}
}
}
// Checks are very basic for now
if( (sTextContent == '') && bMandatory )
{
oResult.is_valid = false;
oResult.error_messages.push(this.options.validators.mandatory.message);
}
}
this.options.on_validation_callback(this, oResult);
return oResult;
}
});
});

View File

@@ -0,0 +1,319 @@
//iTop Portal Form handler
//This extends itop.form_handler
;
$(function()
{
// the widget definition, where 'itop.portal' is the namespace,
// 'form_handler' the widget name
$.widget( 'itop.portal_form_handler', $.itop.form_handler,
{
options: {
submit_url: null,
cancel_url: null
},
// the constructor
_create: function()
{
this.element
.addClass('portal_form_handler');
// Safe check for options
if(this.options.submit_url === "")
this.options.submit_url = null;
if(this.options.cancel_url === "")
this.options.cancel_url = null;
this._super();
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('portal_form_handler');
},
// _setOptions is called with a hash of all options that are changing
// always refresh when changing options
_setOptions: function()
{
this._superApply(arguments);
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._super( key, value );
},
// Overload from parent class
_onSubmitClick: function(oEvent)
{
oEvent.preventDefault();
var me = this;
// Validating fields prior to post (Client side)
var bIsValid = me.options.field_set.triggerHandler('validate');
// Retrieving stimulus name
var sStimulusCode = null;
if($(oEvent.currentTarget).attr('name') === 'stimulus_code')
{
sStimulusCode = $(oEvent.currentTarget).val();
}
// Submit form
if(bIsValid)
{
me._disableFormBeforeLoading();
$.post(
me.options.endpoint,
{
operation: 'submit',
stimulus_code: sStimulusCode,
transaction_id: me.options.formmanager_data.transaction_id,
formmanager_class: me.options.formmanager_class,
formmanager_data: JSON.stringify(me.options.formmanager_data),
current_values: me.getCurrentValues(),
attachment_ids: me.getAttachmentIds()
},
function(oData){
if(oData.form.validation !== undefined)
{
var oValidation = oData.form.validation;
// First we build the form
me.options.field_set.field_set('option', 'fields_list', oData.form.fields_list);
me.options.field_set.field_set('option', 'is_valid', oValidation.valid);
me.options.field_set.field_set('buildForm');
// Then only we display messages from the server, otherwise they will be cleared by the HTML print
var oMessages = oValidation.messages;
// Cleaning help blocks
me.element.find('.form_alerts').removeClass('has-success has-warning has-error');
me.element.find('.form_alerts .alert').html('').hide();
me.element.find('.form_field').removeClass('has-success has-warning has-error');
me.element.find('.form_field .help-block').html('');
// For each type of messages (error, warning, success)...
for(var sMessageType in oMessages)
{
var sMessageClass = 'has-' + sMessageType;
// ... for each concerned fields ...
for(var sFieldId in oMessages[sMessageType])
{
var oField = me.options.field_set.field_set('getField', sFieldId);
var oHelpBlock = null;
// Checking if the messages are for a field or for the whole form
if(oField.length === 1)
{
oField.addClass(sMessageClass);
oHelpBlock = oField.find('.help-block');
}
else
{
oHelpBlock = me.element.find('.form_alerts .alert.alert-' + sMessageType);
oHelpBlock.show();
}
// ... add the message to its help block
for(var i in oMessages[sMessageType][sFieldId])
{
oHelpBlock.append($('<p>' + oMessages[sMessageType][sFieldId][i] + '</p>'));
}
}
}
// Scrolling to top so the user can see messages
$('body').scrollTop(0);
}
// If everything is okay, we close the form and reload it.
if(oValidation.valid)
{
if(me.options.is_modal)
{
me.element.closest('.modal').modal('hide');
}
// Checking if we have to redirect to another page
if(oValidation.redirection !== undefined)
{
var oRedirection = oValidation.redirection;
var bRedirectionAjax = (oRedirection.ajax !== undefined) ? oRedirection.ajax : false;
var sUrl = null;
// URL priority order :
// redirection.url > me.option.submit_url > redirection.alternative_url
if(oRedirection.url !== undefined)
{
sUrl = oRedirection.url;
}
else if(me.options.submit_url !== null)
{
sUrl = me.options.submit_url;
}
else if(oRedirection.alternative_url !== undefined)
{
sUrl = oRedirection.alternative_url;
}
if(sUrl !== null)
{
if(bRedirectionAjax)
{
// Creating a new modal
var oModalElem = $('#modal-for-all').clone();
oModalElem.attr('id', '').appendTo('body');
// Loading content
oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
oModalElem.find('.modal-content').load(sUrl);
oModalElem.modal('show');
}
else
{
setTimeout(function() { location.href = sUrl; }, 400);
}
}
}
else if(me.options.submit_url !== null)
{
setTimeout(function() { location.href = me.options.submit_url; }, 400);
}
}
}
)
.fail(function(oData){
me._onUpdateFailure(oData);
})
.always(function(){
me._enableFormAfterLoading();
});
}
// Else go to the first invalid field
else
{
this.element.find('.has-error')[0].scrollIntoView();
}
},
// Overload from parent class
_onCancelClick: function(oEvent)
{
oEvent.preventDefault();
oEvent.stopPropagation();
var me = this;
// When fields have been modified, we have to ask them to cancel stuff if necessary
if(me.options.field_set.field_set('option', 'touched_fields').length > 0)
{
me._disableFormBeforeLoading();
$.post(
me.options.endpoint,
{
operation: 'cancel',
formmanager_class: me.options.formmanager_class,
formmanager_data: JSON.stringify(me.options.formmanager_data),
current_values: me.getCurrentValues()
},
function(oData)
{
if(me.options.cancel_url !== null)
{
location.href = me.options.cancel_url;
}
}
)
.always(function(){
// Close the modal only if fields had to be cancelled
if(me.options.is_modal)
{
me.element.closest('.modal').modal('hide');
}
me._enableFormAfterLoading();
});
}
// Otherwise we can close the modal immediately
else
{
if(me.options.cancel_url !== null)
{
location.href = me.options.cancel_url;
}
else
{
if(me.options.is_modal)
{
me.element.closest('.modal').modal('hide');
}
else
{
location.reload();
}
}
}
},
// Overload from parent class
_onUpdateFailure: function(oData)
{
if(oData.responseJSON !== undefined && oData.responseJSON !== null)
{
var oResponse = oData.responseJSON;
// If we encounter an error
if(oResponse.exception !== undefined)
{
// Note : This could be refactored for a global use
var oModalElem = $('#modal-for-alert');
oModalElem.find('.modal-title').html(oResponse.error_title);
oModalElem.find('.modal-body .alert').html(oResponse.error_message)
.removeClass('alert-success alert-info alert-warning alert-danger')
.addClass('alert-danger');
oModalElem.modal('show');
}
}
},
// Overload from parent class
_onUpdateAlways: function(oData, sFormPath)
{
// Check all touched AFTER ajax is complete, otherwise the renderer will redraw the field in the mean time.
this.element.find('.form_fields').trigger('validate', {touched_fields_only: true});
this._enableFormAfterLoading();
},
_onFieldChange: function(oEvent, oData)
{
// Clear form help blocks
this.element.find('.form_alerts').removeClass('has-success has-warning has-error');
this.element.find('.form_alerts .alert').html('').hide();
this._super(oEvent, oData);
},
// Place a field for which no container exists
_addField: function(sFieldId)
{
$('<div ' + this.options.field_identifier_attr + '="'+sFieldId+'"></div>').appendTo(this.element.find('.form_fields'));
},
_disableFormBeforeLoading: function()
{
$('#page_overlay').fadeIn(200);
},
_enableFormAfterLoading: function()
{
$('#page_overlay').fadeOut(200);
},
getAttachmentIds: function()
{
var me = this;
var aResult = {actual_attachments_ids: [], removed_attachments_ids: []};
// Actual attachments
this.element.find('.attachments_container :input[name="attachments[]"]').each(function(iIndex, oElement){
aResult.actual_attachments_ids.push($(oElement).val());
});
// Removed attachments
this.element.find('.attachments_container :input[name="removed_attachments[]"]').each(function(iIndex, oElement){
aResult.removed_attachments_ids.push($(oElement).val());
});
return aResult;
}
});
});

View File

@@ -0,0 +1,103 @@
/*!
* Datetimepicker for Bootstrap 3
* version : 4.17.37
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
@font-face {
font-family: 'Glyphicons Halflings';
src: url('../fonts/glyphicons-halflings-regular.eot');
src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.glyphicon-time:before {
content: "\e023";
}
.glyphicon-chevron-left:before {
content: "\e079";
}
.glyphicon-chevron-right:before {
content: "\e080";
}
.glyphicon-chevron-up:before {
content: "\e113";
}
.glyphicon-chevron-down:before {
content: "\e114";
}
.glyphicon-calendar:before {
content: "\e109";
}
.btn {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
}
.collapse {
display: none;
}
.collapse.in {
display: block;
}
.dropdown-menu {
position: absolute;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, .15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
}
.list-unstyled {
padding-left: 0;
list-style: none;
}

View File

@@ -0,0 +1,373 @@
/*!
* Datetimepicker for Bootstrap 3
* version : 4.17.37
* https://github.com/Eonasdan/bootstrap-datetimepicker/
*/
.bootstrap-datetimepicker-widget {
list-style: none;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
margin: 2px 0;
padding: 4px;
width: 19em;
}
@media (min-width: 768px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: 992px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
@media (min-width: 1200px) {
.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs {
width: 38em;
}
}
.bootstrap-datetimepicker-widget.dropdown-menu:before,
.bootstrap-datetimepicker-widget.dropdown-menu:after {
content: '';
display: inline-block;
position: absolute;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-bottom: 7px solid #cccccc;
border-bottom-color: rgba(0, 0, 0, 0.2);
top: -7px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-bottom: 6px solid white;
top: -6px;
left: 8px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:before {
border-left: 7px solid transparent;
border-right: 7px solid transparent;
border-top: 7px solid #cccccc;
border-top-color: rgba(0, 0, 0, 0.2);
bottom: -7px;
left: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.top:after {
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid white;
bottom: -6px;
left: 7px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before {
left: auto;
right: 6px;
}
.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after {
left: auto;
right: 7px;
}
.bootstrap-datetimepicker-widget .list-unstyled {
margin: 0;
}
.bootstrap-datetimepicker-widget a[data-action] {
padding: 6px 0;
}
.bootstrap-datetimepicker-widget a[data-action]:active {
box-shadow: none;
}
.bootstrap-datetimepicker-widget .timepicker-hour,
.bootstrap-datetimepicker-widget .timepicker-minute,
.bootstrap-datetimepicker-widget .timepicker-second {
width: 54px;
font-weight: bold;
font-size: 1.2em;
margin: 0;
}
.bootstrap-datetimepicker-widget button[data-action] {
padding: 6px;
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Increment Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Decrement Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Hours";
}
.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Show Minutes";
}
.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle AM/PM";
}
.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Clear the picker";
}
.bootstrap-datetimepicker-widget .btn[data-action="today"]::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Set the date to today";
}
.bootstrap-datetimepicker-widget .picker-switch {
text-align: center;
}
.bootstrap-datetimepicker-widget .picker-switch::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Toggle Date and Time Screens";
}
.bootstrap-datetimepicker-widget .picker-switch td {
padding: 0;
margin: 0;
height: auto;
width: auto;
line-height: inherit;
}
.bootstrap-datetimepicker-widget .picker-switch td span {
line-height: 2.5;
height: 2.5em;
width: 100%;
}
.bootstrap-datetimepicker-widget table {
width: 100%;
margin: 0;
}
.bootstrap-datetimepicker-widget table td,
.bootstrap-datetimepicker-widget table th {
text-align: center;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table th {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table th.picker-switch {
width: 145px;
}
.bootstrap-datetimepicker-widget table th.disabled,
.bootstrap-datetimepicker-widget table th.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table th.prev::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Previous Month";
}
.bootstrap-datetimepicker-widget table th.next::after {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
content: "Next Month";
}
.bootstrap-datetimepicker-widget table thead tr:first-child th {
cursor: pointer;
}
.bootstrap-datetimepicker-widget table thead tr:first-child th:hover {
background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td {
height: 54px;
line-height: 54px;
width: 54px;
}
.bootstrap-datetimepicker-widget table td.cw {
font-size: .8em;
height: 20px;
line-height: 20px;
color: #777777;
}
.bootstrap-datetimepicker-widget table td.day {
height: 20px;
line-height: 20px;
width: 20px;
}
.bootstrap-datetimepicker-widget table td.day:hover,
.bootstrap-datetimepicker-widget table td.hour:hover,
.bootstrap-datetimepicker-widget table td.minute:hover,
.bootstrap-datetimepicker-widget table td.second:hover {
background: #eeeeee;
cursor: pointer;
}
.bootstrap-datetimepicker-widget table td.old,
.bootstrap-datetimepicker-widget table td.new {
color: #777777;
}
.bootstrap-datetimepicker-widget table td.today {
position: relative;
}
.bootstrap-datetimepicker-widget table td.today:before {
content: '';
display: inline-block;
border: solid transparent;
border-width: 0 0 7px 7px;
border-bottom-color: #337ab7;
border-top-color: rgba(0, 0, 0, 0.2);
position: absolute;
bottom: 4px;
right: 4px;
}
.bootstrap-datetimepicker-widget table td.active,
.bootstrap-datetimepicker-widget table td.active:hover {
background-color: #337ab7;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td.active.today:before {
border-bottom-color: #fff;
}
.bootstrap-datetimepicker-widget table td.disabled,
.bootstrap-datetimepicker-widget table td.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget table td span {
display: inline-block;
width: 54px;
height: 54px;
line-height: 54px;
margin: 2px 1.5px;
cursor: pointer;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget table td span:hover {
background: #eeeeee;
}
.bootstrap-datetimepicker-widget table td span.active {
background-color: #337ab7;
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.bootstrap-datetimepicker-widget table td span.old {
color: #777777;
}
.bootstrap-datetimepicker-widget table td span.disabled,
.bootstrap-datetimepicker-widget table td span.disabled:hover {
background: none;
color: #777777;
cursor: not-allowed;
}
.bootstrap-datetimepicker-widget.usetwentyfour td.hour {
height: 27px;
line-height: 27px;
}
.bootstrap-datetimepicker-widget.wider {
width: 21em;
}
.bootstrap-datetimepicker-widget .datepicker-decades .decade {
line-height: 1.8em !important;
}
.input-group.date .input-group-addon {
cursor: pointer;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,288 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
<font-face units-per-em="1200" ascent="960" descent="-240" />
<missing-glyph horiz-adv-x="500" />
<glyph horiz-adv-x="0" />
<glyph horiz-adv-x="400" />
<glyph unicode=" " />
<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" />
<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xa0;" />
<glyph unicode="&#xa5;" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" />
<glyph unicode="&#x2000;" horiz-adv-x="650" />
<glyph unicode="&#x2001;" horiz-adv-x="1300" />
<glyph unicode="&#x2002;" horiz-adv-x="650" />
<glyph unicode="&#x2003;" horiz-adv-x="1300" />
<glyph unicode="&#x2004;" horiz-adv-x="433" />
<glyph unicode="&#x2005;" horiz-adv-x="325" />
<glyph unicode="&#x2006;" horiz-adv-x="216" />
<glyph unicode="&#x2007;" horiz-adv-x="216" />
<glyph unicode="&#x2008;" horiz-adv-x="162" />
<glyph unicode="&#x2009;" horiz-adv-x="260" />
<glyph unicode="&#x200a;" horiz-adv-x="72" />
<glyph unicode="&#x202f;" horiz-adv-x="260" />
<glyph unicode="&#x205f;" horiz-adv-x="325" />
<glyph unicode="&#x20ac;" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" />
<glyph unicode="&#x20bd;" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" />
<glyph unicode="&#x2212;" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#x231b;" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" />
<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
<glyph unicode="&#x2601;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" />
<glyph unicode="&#x26fa;" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " />
<glyph unicode="&#x2709;" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" />
<glyph unicode="&#x270f;" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" />
<glyph unicode="&#xe001;" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" />
<glyph unicode="&#xe002;" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" />
<glyph unicode="&#xe003;" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" />
<glyph unicode="&#xe005;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" />
<glyph unicode="&#xe006;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" />
<glyph unicode="&#xe007;" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" />
<glyph unicode="&#xe008;" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" />
<glyph unicode="&#xe009;" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" />
<glyph unicode="&#xe010;" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe011;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" />
<glyph unicode="&#xe012;" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe013;" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" />
<glyph unicode="&#xe014;" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" />
<glyph unicode="&#xe015;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe016;" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe017;" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" />
<glyph unicode="&#xe018;" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe019;" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" />
<glyph unicode="&#xe020;" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" />
<glyph unicode="&#xe021;" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" />
<glyph unicode="&#xe022;" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" />
<glyph unicode="&#xe023;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe024;" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" />
<glyph unicode="&#xe025;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe026;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " />
<glyph unicode="&#xe027;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" />
<glyph unicode="&#xe028;" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" />
<glyph unicode="&#xe029;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
<glyph unicode="&#xe030;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" />
<glyph unicode="&#xe031;" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" />
<glyph unicode="&#xe032;" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe033;" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" />
<glyph unicode="&#xe034;" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" />
<glyph unicode="&#xe035;" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" />
<glyph unicode="&#xe036;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" />
<glyph unicode="&#xe037;" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" />
<glyph unicode="&#xe038;" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" />
<glyph unicode="&#xe039;" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" />
<glyph unicode="&#xe040;" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" />
<glyph unicode="&#xe041;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
<glyph unicode="&#xe042;" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
<glyph unicode="&#xe043;" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" />
<glyph unicode="&#xe044;" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe045;" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" />
<glyph unicode="&#xe046;" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" />
<glyph unicode="&#xe047;" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" />
<glyph unicode="&#xe048;" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" />
<glyph unicode="&#xe049;" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" />
<glyph unicode="&#xe050;" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" />
<glyph unicode="&#xe051;" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" />
<glyph unicode="&#xe052;" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe053;" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe054;" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" />
<glyph unicode="&#xe055;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe056;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe057;" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe058;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe059;" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" />
<glyph unicode="&#xe060;" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" />
<glyph unicode="&#xe062;" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" />
<glyph unicode="&#xe063;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" />
<glyph unicode="&#xe064;" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" />
<glyph unicode="&#xe065;" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" />
<glyph unicode="&#xe066;" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" />
<glyph unicode="&#xe067;" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" />
<glyph unicode="&#xe068;" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" />
<glyph unicode="&#xe069;" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe070;" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe071;" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" />
<glyph unicode="&#xe072;" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" />
<glyph unicode="&#xe073;" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe074;" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" />
<glyph unicode="&#xe075;" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" />
<glyph unicode="&#xe076;" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe077;" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe078;" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe079;" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" />
<glyph unicode="&#xe080;" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" />
<glyph unicode="&#xe081;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" />
<glyph unicode="&#xe082;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" />
<glyph unicode="&#xe083;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" />
<glyph unicode="&#xe084;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" />
<glyph unicode="&#xe085;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe086;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe087;" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" />
<glyph unicode="&#xe088;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" />
<glyph unicode="&#xe089;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" />
<glyph unicode="&#xe090;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" />
<glyph unicode="&#xe091;" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" />
<glyph unicode="&#xe092;" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" />
<glyph unicode="&#xe093;" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" />
<glyph unicode="&#xe094;" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe095;" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" />
<glyph unicode="&#xe096;" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" />
<glyph unicode="&#xe097;" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" />
<glyph unicode="&#xe101;" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe102;" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" />
<glyph unicode="&#xe103;" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" />
<glyph unicode="&#xe104;" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" />
<glyph unicode="&#xe105;" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
<glyph unicode="&#xe106;" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
<glyph unicode="&#xe107;" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" />
<glyph unicode="&#xe108;" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" />
<glyph unicode="&#xe109;" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" />
<glyph unicode="&#xe110;" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" />
<glyph unicode="&#xe111;" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" />
<glyph unicode="&#xe112;" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" />
<glyph unicode="&#xe113;" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" />
<glyph unicode="&#xe114;" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" />
<glyph unicode="&#xe115;" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
<glyph unicode="&#xe116;" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" />
<glyph unicode="&#xe117;" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
<glyph unicode="&#xe118;" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" />
<glyph unicode="&#xe119;" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
<glyph unicode="&#xe120;" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" />
<glyph unicode="&#xe121;" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" />
<glyph unicode="&#xe122;" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" />
<glyph unicode="&#xe123;" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" />
<glyph unicode="&#xe124;" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" />
<glyph unicode="&#xe125;" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe126;" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" />
<glyph unicode="&#xe127;" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe128;" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe129;" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe130;" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" />
<glyph unicode="&#xe131;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" />
<glyph unicode="&#xe132;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" />
<glyph unicode="&#xe133;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" />
<glyph unicode="&#xe134;" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe135;" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" />
<glyph unicode="&#xe136;" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" />
<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
<glyph unicode="&#xe138;" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" />
<glyph unicode="&#xe139;" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
<glyph unicode="&#xe140;" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" />
<glyph unicode="&#xe141;" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" />
<glyph unicode="&#xe142;" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" />
<glyph unicode="&#xe143;" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" />
<glyph unicode="&#xe144;" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" />
<glyph unicode="&#xe145;" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" />
<glyph unicode="&#xe146;" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" />
<glyph unicode="&#xe148;" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" />
<glyph unicode="&#xe149;" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" />
<glyph unicode="&#xe150;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
<glyph unicode="&#xe151;" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " />
<glyph unicode="&#xe152;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " />
<glyph unicode="&#xe153;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" />
<glyph unicode="&#xe154;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" />
<glyph unicode="&#xe155;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
<glyph unicode="&#xe156;" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
<glyph unicode="&#xe157;" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" />
<glyph unicode="&#xe158;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
<glyph unicode="&#xe159;" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" />
<glyph unicode="&#xe160;" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" />
<glyph unicode="&#xe161;" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
<glyph unicode="&#xe162;" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" />
<glyph unicode="&#xe163;" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
<glyph unicode="&#xe164;" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" />
<glyph unicode="&#xe165;" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
<glyph unicode="&#xe166;" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
<glyph unicode="&#xe167;" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
<glyph unicode="&#xe168;" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" />
<glyph unicode="&#xe169;" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
<glyph unicode="&#xe170;" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
<glyph unicode="&#xe171;" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" />
<glyph unicode="&#xe172;" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" />
<glyph unicode="&#xe173;" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" />
<glyph unicode="&#xe174;" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" />
<glyph unicode="&#xe175;" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe176;" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" />
<glyph unicode="&#xe177;" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" />
<glyph unicode="&#xe178;" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" />
<glyph unicode="&#xe179;" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" />
<glyph unicode="&#xe180;" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" />
<glyph unicode="&#xe181;" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" />
<glyph unicode="&#xe182;" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" />
<glyph unicode="&#xe183;" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" />
<glyph unicode="&#xe184;" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe185;" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " />
<glyph unicode="&#xe186;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
<glyph unicode="&#xe187;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
<glyph unicode="&#xe188;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" />
<glyph unicode="&#xe189;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" />
<glyph unicode="&#xe190;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" />
<glyph unicode="&#xe191;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" />
<glyph unicode="&#xe192;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" />
<glyph unicode="&#xe193;" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" />
<glyph unicode="&#xe194;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
<glyph unicode="&#xe195;" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" />
<glyph unicode="&#xe197;" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe198;" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" />
<glyph unicode="&#xe199;" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" />
<glyph unicode="&#xe200;" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" />
<glyph unicode="&#xe201;" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" />
<glyph unicode="&#xe202;" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" />
<glyph unicode="&#xe203;" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" />
<glyph unicode="&#xe204;" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" />
<glyph unicode="&#xe205;" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" />
<glyph unicode="&#xe206;" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" />
<glyph unicode="&#xe209;" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" />
<glyph unicode="&#xe210;" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe211;" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe212;" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe213;" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe214;" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe215;" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe216;" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" />
<glyph unicode="&#xe218;" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" />
<glyph unicode="&#xe219;" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" />
<glyph unicode="&#xe221;" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe223;" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" />
<glyph unicode="&#xe224;" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " />
<glyph unicode="&#xe225;" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" />
<glyph unicode="&#xe226;" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" />
<glyph unicode="&#xe227;" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" />
<glyph unicode="&#xe230;" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" />
<glyph unicode="&#xe231;" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
<glyph unicode="&#xe232;" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
<glyph unicode="&#xe233;" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" />
<glyph unicode="&#xe234;" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
<glyph unicode="&#xe235;" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
<glyph unicode="&#xe236;" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
<glyph unicode="&#xe237;" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" />
<glyph unicode="&#xe238;" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe239;" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" />
<glyph unicode="&#xe240;" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" />
<glyph unicode="&#xe241;" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" />
<glyph unicode="&#xe242;" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
<glyph unicode="&#xe243;" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" />
<glyph unicode="&#xe244;" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" />
<glyph unicode="&#xe245;" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" />
<glyph unicode="&#xe246;" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" />
<glyph unicode="&#xe247;" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe248;" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" />
<glyph unicode="&#xe249;" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
<glyph unicode="&#xe250;" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" />
<glyph unicode="&#xe251;" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" />
<glyph unicode="&#xe252;" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" />
<glyph unicode="&#xe253;" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" />
<glyph unicode="&#xe254;" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" />
<glyph unicode="&#xe255;" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" />
<glyph unicode="&#xe256;" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" />
<glyph unicode="&#xe257;" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" />
<glyph unicode="&#xe258;" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" />
<glyph unicode="&#xe259;" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" />
<glyph unicode="&#xe260;" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" />
<glyph unicode="&#xf8ff;" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" />
<glyph unicode="&#x1f511;" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" />
<glyph unicode="&#x1f6aa;" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
require('../../js/transition.js')
require('../../js/alert.js')
require('../../js/button.js')
require('../../js/carousel.js')
require('../../js/collapse.js')
require('../../js/dropdown.js')
require('../../js/modal.js')
require('../../js/tooltip.js')
require('../../js/popover.js')
require('../../js/scrollspy.js')
require('../../js/tab.js')
require('../../js/affix.js')

View File

@@ -0,0 +1 @@
table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable{border-collapse:separate !important}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}

View File

@@ -0,0 +1 @@
table.dataTable.fixedHeader-floating,table.dataTable.fixedHeader-locked{background-color:white;margin-top:0 !important;margin-bottom:0 !important}table.dataTable.fixedHeader-floating{position:fixed}table.dataTable.fixedHeader-locked{position:absolute}

View File

@@ -0,0 +1 @@
table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before{top:8px;left:4px;height:16px;width:16px;display:block;position:absolute;color:white;border:2px solid white;border-radius:16px;text-align:center;line-height:14px;box-shadow:0 0 3px #444;box-sizing:content-box;content:'+';background-color:#337ab7}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child.dataTables_empty:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child.dataTables_empty:before{display:none}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before{content:'-';background-color:#d33333}table.dataTable.dtr-inline.collapsed>tbody>tr.child td:before{display:none}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child:before{top:5px;left:4px;height:14px;width:14px;border-radius:14px;line-height:12px}table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:16px;width:16px;margin-top:-10px;margin-left:-10px;display:block;position:absolute;color:white;border:2px solid white;border-radius:16px;text-align:center;line-height:14px;box-shadow:0 0 3px #444;box-sizing:content-box;content:'+';background-color:#337ab7}table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:'-';background-color:#d33333}table.dataTable>tbody>tr.child{padding:0.5em 1em}table.dataTable>tbody>tr.child:hover{background:transparent !important}table.dataTable>tbody>tr.child ul{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul li{border-bottom:1px solid #efefef;padding:0.5em 0}table.dataTable>tbody>tr.child ul li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:bold}

View File

@@ -0,0 +1 @@
div.DTS tbody th,div.DTS tbody td{white-space:nowrap}div.DTS tbody tr.even{background-color:white}div.DTS div.DTS_Loading{z-index:1}div.DTS div.dataTables_scrollBody{background:repeating-linear-gradient(45deg, #edeeff, #edeeff 10px, #fff 10px, #fff 20px)}div.DTS div.dataTables_scrollBody table{z-index:2}div.DTS div.dataTables_paginate{display:none}

View File

@@ -0,0 +1 @@
table.dataTable tbody>tr.selected,table.dataTable tbody>tr>.selected{background-color:#08C}table.dataTable.stripe tbody>tr.odd.selected,table.dataTable.stripe tbody>tr.odd>.selected,table.dataTable.display tbody>tr.odd.selected,table.dataTable.display tbody>tr.odd>.selected{background-color:#0085c7}table.dataTable.hover tbody>tr.selected:hover,table.dataTable.hover tbody>tr>.selected:hover,table.dataTable.display tbody>tr.selected:hover,table.dataTable.display tbody>tr>.selected:hover{background-color:#0083c5}table.dataTable.order-column tbody>tr.selected>.sorting_1,table.dataTable.order-column tbody>tr.selected>.sorting_2,table.dataTable.order-column tbody>tr.selected>.sorting_3,table.dataTable.order-column tbody>tr>.selected,table.dataTable.display tbody>tr.selected>.sorting_1,table.dataTable.display tbody>tr.selected>.sorting_2,table.dataTable.display tbody>tr.selected>.sorting_3,table.dataTable.display tbody>tr>.selected{background-color:#0085c8}table.dataTable.display tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_1{background-color:#0081c1}table.dataTable.display tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_2{background-color:#0082c2}table.dataTable.display tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_3{background-color:#0083c4}table.dataTable.display tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_1{background-color:#0085c8}table.dataTable.display tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_2{background-color:#0086ca}table.dataTable.display tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_3{background-color:#0087cb}table.dataTable.display tbody>tr.odd>.selected,table.dataTable.order-column.stripe tbody>tr.odd>.selected{background-color:#0081c1}table.dataTable.display tbody>tr.even>.selected,table.dataTable.order-column.stripe tbody>tr.even>.selected{background-color:#0085c8}table.dataTable.display tbody>tr.selected:hover>.sorting_1,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_1{background-color:#007dbb}table.dataTable.display tbody>tr.selected:hover>.sorting_2,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_2{background-color:#007ebd}table.dataTable.display tbody>tr.selected:hover>.sorting_3,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_3{background-color:#007fbf}table.dataTable.display tbody>tr:hover>.selected,table.dataTable.display tbody>tr>.selected:hover,table.dataTable.order-column.hover tbody>tr:hover>.selected,table.dataTable.order-column.hover tbody>tr>.selected:hover{background-color:#007dbb}table.dataTable td.select-checkbox{position:relative}table.dataTable td.select-checkbox:before,table.dataTable td.select-checkbox:after{display:block;position:absolute;top:1.2em;left:50%;width:12px;height:12px;box-sizing:border-box}table.dataTable td.select-checkbox:before{content:' ';margin-top:-6px;margin-left:-6px;border:1px solid black;border-radius:3px}table.dataTable tr.selected td.select-checkbox:after{content:'\2714';margin-top:-11px;margin-left:-4px;text-align:center;text-shadow:1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9}div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0.5em}@media screen and (max-width: 640px){div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0;display:block}}table.dataTable tbody tr.selected,table.dataTable tbody th.selected,table.dataTable tbody td.selected{color:white}table.dataTable tbody tr.selected a,table.dataTable tbody th.selected a,table.dataTable tbody td.selected a{color:#a2d4ed}

View File

@@ -0,0 +1 @@
table.dataTable tbody>tr.selected,table.dataTable tbody>tr>.selected{background-color:#B0BED9}table.dataTable.stripe tbody>tr.odd.selected,table.dataTable.stripe tbody>tr.odd>.selected,table.dataTable.display tbody>tr.odd.selected,table.dataTable.display tbody>tr.odd>.selected{background-color:#acbad4}table.dataTable.hover tbody>tr.selected:hover,table.dataTable.hover tbody>tr>.selected:hover,table.dataTable.display tbody>tr.selected:hover,table.dataTable.display tbody>tr>.selected:hover{background-color:#aab7d1}table.dataTable.order-column tbody>tr.selected>.sorting_1,table.dataTable.order-column tbody>tr.selected>.sorting_2,table.dataTable.order-column tbody>tr.selected>.sorting_3,table.dataTable.order-column tbody>tr>.selected,table.dataTable.display tbody>tr.selected>.sorting_1,table.dataTable.display tbody>tr.selected>.sorting_2,table.dataTable.display tbody>tr.selected>.sorting_3,table.dataTable.display tbody>tr>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody>tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody>tr.odd>.selected,table.dataTable.order-column.stripe tbody>tr.odd>.selected{background-color:#a6b4cd}table.dataTable.display tbody>tr.even>.selected,table.dataTable.order-column.stripe tbody>tr.even>.selected{background-color:#acbad5}table.dataTable.display tbody>tr.selected:hover>.sorting_1,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody>tr.selected:hover>.sorting_2,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody>tr.selected:hover>.sorting_3,table.dataTable.order-column.hover tbody>tr.selected:hover>.sorting_3{background-color:#a5b2cb}table.dataTable.display tbody>tr:hover>.selected,table.dataTable.display tbody>tr>.selected:hover,table.dataTable.order-column.hover tbody>tr:hover>.selected,table.dataTable.order-column.hover tbody>tr>.selected:hover{background-color:#a2aec7}table.dataTable td.select-checkbox{position:relative}table.dataTable td.select-checkbox:before,table.dataTable td.select-checkbox:after{display:block;position:absolute;top:1.2em;left:50%;width:12px;height:12px;box-sizing:border-box}table.dataTable td.select-checkbox:before{content:' ';margin-top:-6px;margin-left:-6px;border:1px solid black;border-radius:3px}table.dataTable tr.selected td.select-checkbox:after{content:'\2714';margin-top:-11px;margin-left:-4px;text-align:center;text-shadow:1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9}div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0.5em}@media screen and (max-width: 640px){div.dataTables_wrapper span.select-info,div.dataTables_wrapper span.select-item{margin-left:0;display:block}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View File

@@ -0,0 +1,8 @@
/*!
DataTables Bootstrap 3 integration
©2011-2014 SpryMedia Ltd - datatables.net/license
*/
(function(l,q){var d=function(b,c){b.extend(!0,c.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});b.extend(c.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"});c.ext.renderer.pageButton.bootstrap=function(g,d,r,s,i,m){var t=new c.Api(g),u=g.oClasses,j=g.oLanguage.oPaginate,e,f,n=0,p=function(c,d){var k,h,o,a,l=function(a){a.preventDefault();
b(a.currentTarget).hasClass("disabled")||t.page(a.data.action).draw("page")};k=0;for(h=d.length;k<h;k++)if(a=d[k],b.isArray(a))p(c,a);else{f=e="";switch(a){case "ellipsis":e="&hellip;";f="disabled";break;case "first":e=j.sFirst;f=a+(0<i?"":" disabled");break;case "previous":e=j.sPrevious;f=a+(0<i?"":" disabled");break;case "next":e=j.sNext;f=a+(i<m-1?"":" disabled");break;case "last":e=j.sLast;f=a+(i<m-1?"":" disabled");break;default:e=a+1,f=i===a?"active":""}e&&(o=b("<li>",{"class":u.sPageButton+
" "+f,id:0===r&&"string"===typeof a?g.sTableId+"_"+a:null}).append(b("<a>",{href:"#","aria-controls":g.sTableId,"data-dt-idx":n,tabindex:g.iTabIndex}).html(e)).appendTo(c),g.oApi._fnBindAction(o,{action:a},l),n++)}},h;try{h=b(d).find(q.activeElement).data("dt-idx")}catch(l){}p(b(d).empty().html('<ul class="pagination"/>').children("ul"),s);h&&b(d).find("[data-dt-idx="+h+"]").focus()};c.TableTools&&(b.extend(!0,c.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn btn-default",disabled:"disabled"},
collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info"},select:{row:"active"}}),b.extend(!0,c.TableTools.DEFAULTS.oTags,{collection:{container:"ul",button:"li",liner:"a"}}))};"function"===typeof define&&define.amd?define(["jquery","datatables"],d):"object"===typeof exports?d(require("jquery"),require("datatables")):jQuery&&d(jQuery,jQuery.fn.dataTable)})(window,document);

View File

@@ -0,0 +1,14 @@
/*!
FixedHeader 3.0.0
©2009-2015 SpryMedia Ltd - datatables.net/license
*/
(function(h,j){var g=function(e,i){var g=0,f=function(b,a){if(!(this instanceof f))throw"FixedHeader must be initialised with the 'new' keyword.";!0===a&&(a={});b=new i.Api(b);this.c=e.extend(!0,{},f.defaults,a);this.s={dt:b,position:{theadTop:0,tbodyTop:0,tfootTop:0,tfootBottom:0,width:0,left:0,tfootHeight:0,theadHeight:0,windowHeight:e(h).height(),visible:!0},headerMode:null,footerMode:null,namespace:".dtfc"+g++};this.dom={floatingHeader:null,thead:e(b.table().header()),tbody:e(b.table().body()),
tfoot:e(b.table().footer()),header:{host:null,floating:null,placeholder:null},footer:{host:null,floating:null,placeholder:null}};this.dom.header.host=this.dom.thead.parent();this.dom.footer.host=this.dom.tfoot.parent();var c=b.settings()[0];if(c._fixedHeader)throw"FixedHeader already initialised on table "+c.nTable.id;c._fixedHeader=this;this._constructor()};f.prototype={update:function(){this._positions();this._scroll(!0)},_constructor:function(){var b=this,a=this.s.dt;e(h).on("scroll"+this.s.namespace,
function(){b._scroll()}).on("resize"+this.s.namespace,function(){b.s.position.windowHeight=e(h).height();b._positions();b._scroll(!0)});a.on("column-reorder.dt.dtfc column-visibility.dt.dtfc",function(){b._positions();b._scroll(!0)}).on("draw.dtfc",function(){b._positions();b._scroll()});a.on("destroy.dtfc",function(){a.off(".dtfc");e(h).off(this.s.namespace)});this._positions();this._scroll()},_clone:function(b,a){var c=this.s.dt,d=this.dom[b],k="header"===b?this.dom.thead:this.dom.tfoot;!a&&d.floating?
d.floating.removeClass("fixedHeader-floating fixedHeader-locked"):(d.floating&&(d.placeholder.remove(),d.floating.children().detach(),d.floating.remove()),d.floating=e(c.table().node().cloneNode(!1)).removeAttr("id").append(k).appendTo("body"),d.placeholder=k.clone(!1),d.host.append(d.placeholder),"footer"===b&&this._footerMatch(d.placeholder,d.floating))},_footerMatch:function(b,a){var c=function(d){var c=e(d,b).map(function(){return e(this).width()}).toArray();e(d,a).each(function(a){e(this).width(c[a])})};
c("th");c("td")},_footerUnsize:function(){var b=this.dom.footer.floating;b&&e("th, td",b).css("width","")},_modeChange:function(b,a,c){var d=this.dom[a],e=this.s.position;"in-place"===b?(d.placeholder&&(d.placeholder.remove(),d.placeholder=null),d.host.append("header"===a?this.dom.thead:this.dom.tfoot),d.floating&&(d.floating.remove(),d.floating=null),"footer"===a&&this._footerUnsize()):"in"===b?(this._clone(a,c),d.floating.addClass("fixedHeader-floating").css("header"===a?"top":"bottom",this.c[a+
"Offset"]).css("left",e.left+"px").css("width",e.width+"px"),"footer"===a&&d.floating.css("top","")):"below"===b?(this._clone(a,c),d.floating.addClass("fixedHeader-locked").css("top",e.tfootTop-e.theadHeight).css("left",e.left+"px").css("width",e.width+"px")):"above"===b&&(this._clone(a,c),d.floating.addClass("fixedHeader-locked").css("top",e.tbodyTop).css("left",e.left+"px").css("width",e.width+"px"));this.s[a+"Mode"]=b},_positions:function(){var b=this.s.dt.table(),a=this.s.position,c=this.dom,
b=e(b.node()),d=b.children("thead"),f=b.children("tfoot"),c=c.tbody;a.visible=b.is(":visible");a.width=b.outerWidth();a.left=b.offset().left;a.theadTop=d.offset().top;a.tbodyTop=c.offset().top;a.theadHeight=a.tbodyTop-a.theadTop;f.length?(a.tfootTop=f.offset().top,a.tfootBottom=a.tfootTop+f.outerHeight(),a.tfootHeight=a.tfootBottom-a.tfootTop):(a.tfootTop=a.tbodyTop+c.outerHeight(),a.tfootBottom=a.tfootTop,a.tfootHeight=a.tfootTop)},_scroll:function(b){var a=e(j).scrollTop(),c=this.s.position,d;this.c.header&&
(d=!c.visible||a<=c.theadTop-this.c.headerOffset?"in-place":a<=c.tfootTop-c.theadHeight-this.c.headerOffset?"in":"below",(b||d!==this.s.headerMode)&&this._modeChange(d,"header",b));this.c.footer&&this.dom.tfoot.length&&(a=!c.visible||a+c.windowHeight>=c.tfootBottom+this.c.footerOffset?"in-place":c.windowHeight+a>c.tbodyTop+c.tfootHeight+this.c.footerOffset?"in":"above",(b||a!==this.s.footerMode)&&this._modeChange(a,"footer",b))}};f.version="3.0.0";f.defaults={header:!0,footer:!1,headerOffset:0,footerOffset:0};
e.fn.dataTable.FixedHeader=f;e.fn.DataTable.FixedHeader=f;e(j).on("init.dt.dtb",function(b,a){if("dt"===b.namespace){var c=a.oInit.fixedHeader||i.defaults.fixedHeader;c&&!a._buttons&&new f(a,c)}});i.Api.register("fixedHeader()",function(){});i.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(b){(b=b._fixedHeader)&&b.update()})});return f};"function"===typeof define&&define.amd?define(["jquery","datatables"],g):"object"===typeof exports?g(require("jquery"),require("datatables")):
jQuery&&!jQuery.fn.dataTable.FixedHeader&&g(jQuery,jQuery.fn.dataTable)})(window,document);

Some files were not shown because too many files have changed in this diff Show More