├── screenshots └── main.jpg ├── css ├── fa-4.7.0 │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ └── css │ │ └── font-awesome.min.css ├── tutorial.css ├── settings.css └── main.css ├── js ├── templates │ ├── main_navigation_header.handlebars │ ├── main_tutorial.handlebars │ ├── main_remove_group.handlebars │ ├── main_add_group.handlebars │ ├── main_navigation.handlebars │ └── main_content.handlebars ├── templates.js ├── settings.js └── main.js ├── img ├── remove.svg ├── search.svg └── settings.svg ├── appinfo ├── app.php ├── database.xml ├── info.xml └── routes.php ├── README.md ├── lib ├── Settings │ └── Admin.php ├── AppInfo │ └── Application.php └── Controller │ └── SettingsController.php ├── templates ├── main.php └── settings.php ├── l10n ├── de.json ├── de_DE.json ├── de.js └── de_DE.js └── LICENSE /screenshots/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/HEAD/screenshots/main.jpg -------------------------------------------------------------------------------- /css/fa-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/HEAD/css/fa-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /css/fa-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/HEAD/css/fa-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /css/fa-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/HEAD/css/fa-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /js/templates/main_navigation_header.handlebars: -------------------------------------------------------------------------------- 1 | {{addGroupTXT}} 2 | -------------------------------------------------------------------------------- /css/fa-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/HEAD/css/fa-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /css/fa-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/HEAD/css/fa-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /js/templates/main_tutorial.handlebars: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /js/templates/main_remove_group.handlebars: -------------------------------------------------------------------------------- 1 |
2 |

{{questionTXT}}

3 |
4 | 5 |
6 | 7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /js/templates/main_add_group.handlebars: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /img/remove.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /appinfo/app.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Hornig Software 2017 10 | */ 11 | 12 | use OCA\LdapOrg\AppInfo\Application; 13 | 14 | $app = new Application(); 15 | $app->registerNavigation(); -------------------------------------------------------------------------------- /js/templates/main_navigation.handlebars: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LdapOrg 2 | Manage your LDAP users and groups directly in Nextcloud 3 | 4 | ### Features 5 | * Create and delete your LDAP users and groups with one click. 6 | * Ability to send a welcome and seting password E-Mail automatically when creating a new user. 7 | * Users can see all the groups they are a member of and all the other members in their groups 8 | 9 | ### Requirements 10 | * [LdapContacts](https://github.com/KircheNeuenburg/LdapContacts) 11 | * active LDAP authentification (check [Nextcloud admin manual](https://docs.nextcloud.com/server/13/admin_manual/configuration_user/user_auth_ldap.html) for details) -------------------------------------------------------------------------------- /img/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /appinfo/database.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | *dbname* 4 | true 5 | false 6 | utf8 7 | 8 | 9 | *dbprefix*ldaporg_group_admins 10 | 11 | 12 | id 13 | integer 14 | 0 15 | true 16 | 1 17 | 11 18 | 19 | 20 | group_id 21 | text 22 | true 23 | 255 24 | 25 | 26 | admin_id 27 | text 28 | true 29 | 255 30 | 31 | 32 |
33 |
-------------------------------------------------------------------------------- /img/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/Settings/Admin.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Hornig Software 2017 10 | */ 11 | 12 | namespace OCA\LdapOrg\Settings; 13 | 14 | use OCP\AppFramework\Http\TemplateResponse; 15 | use OCP\Settings\ISettings; 16 | 17 | Class Admin implements ISettings { 18 | /** 19 | * @return TemplateResponse 20 | */ 21 | public function getForm() { 22 | return new TemplateResponse( 'ldaporg', 'settings' ); 23 | } 24 | 25 | /** 26 | * @return string the section ID, e.g. 'sharing' 27 | */ 28 | public function getSection() { 29 | return 'ldap'; 30 | } 31 | 32 | /** 33 | * @return int whether the form should be rather on the top or bottom of 34 | * the admin section. The forms are arranged in ascending order of the 35 | * priority values. It is required to return a value between 0 and 100. 36 | * 37 | * E.g.: 70 38 | */ 39 | public function getPriority() { 40 | return 70; 41 | } 42 | } -------------------------------------------------------------------------------- /appinfo/info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | ldaporg 4 | LDAP Organisation 5 | Lets you easily create and edit your LDAP users and groups. 6 | 17 | 1.2.1 18 | AGPL 19 | Alexander Hornig 20 | LdapOrg 21 | 22 | 23 | 24 | 25 | 26 | tools 27 | 28 | https://github.com/KircheNeuenburg/ldaporg 29 | 30 | https://github.com/KircheNeuenburg/ldaporg/issues 31 | 32 | https://github.com/KircheNeuenburg/ldaporg.git 33 | 34 | https://raw.githubusercontent.com/KircheNeuenburg/ldaporg/master/screenshots/main.jpg 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | OCA\LdapOrg\Settings\Admin 44 | 45 | 46 | -------------------------------------------------------------------------------- /templates/main.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Hornig Software 2017 10 | */ 11 | 12 | /** @var \OCP\IL10N $l */ 13 | 14 | script( 'ldaporg', 'main' ); 15 | script('ldaporg', 'templates'); 16 | style( 'ldaporg', 'main' ); 17 | style( 'ldaporg', 'tutorial' ); 18 | // load font awesome icons 19 | style( 'ldaporg', 'fa-4.7.0/css/font-awesome.min' ); 20 | ?> 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 55 | -------------------------------------------------------------------------------- /lib/AppInfo/Application.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Hornig Software 2017 10 | */ 11 | 12 | namespace OCA\LdapOrg\AppInfo; 13 | 14 | use OCP\AppFramework\App; 15 | use OCA\LdapOrg\Controller\PageController; 16 | use OCA\LdapOrg\Controller\SettingsController; 17 | use OCA\LdapOrg\Controller\ContactController; 18 | use OCA\LdapOrg\Settings\Admin; 19 | 20 | Class Application extends App { 21 | /** 22 | * @param array $urlParams 23 | */ 24 | public function __construct( $urlParams = array() ) { 25 | parent::__construct( 'ldaporg', $urlParams ); 26 | // register the apps services 27 | $this->registerServices(); 28 | } 29 | 30 | /** 31 | * register all required services 32 | */ 33 | private function registerServices() { 34 | $container = $this->getContainer(); 35 | 36 | $container->registerAlias( 'SettingsController', SettingsController::class); 37 | $container->registerAlias( 'PageController', PageController::class); 38 | $container->registerAlias( 'Admin', Admin::class); 39 | } 40 | 41 | /** 42 | * register the navigation button 43 | */ 44 | public function registerNavigation() { 45 | $container = $this->getContainer(); 46 | 47 | // add menu entry 48 | $container->query('OCP\INavigationManager')->add(function () use ($container) { 49 | $urlGenerator = $container->query('OCP\IURLGenerator'); 50 | $l10n = $container->query('OCP\IL10N'); 51 | return [ 52 | // the string under which your app will be referenced in owncloud 53 | 'id' => 'ldaporg', 54 | 55 | // sorting weight for the navigation. The higher the number, the higher 56 | // will it be listed in the navigation 57 | 'order' => 100, 58 | 59 | // the route that will be shown on startup 60 | 'href' => $urlGenerator->linkToRoute('ldaporg.Page.index'), 61 | 62 | // the icon that will be shown in the navigation 63 | // this file needs to exist in img/ 64 | 'icon' => $urlGenerator->imagePath('ldaporg', 'settings.svg'), 65 | 66 | // the title of your application. This will be used in the 67 | // navigation or on the settings page of your app 68 | 'name' => $l10n->t('Groups'), 69 | ]; 70 | }); 71 | } 72 | } -------------------------------------------------------------------------------- /js/templates/main_content.handlebars: -------------------------------------------------------------------------------- 1 | {{#if group}} 2 |

{{ group.ldapcontacts_name }}

3 | 4 |
5 | {{#if group.ldaporg_canedit}} 6 | 7 |
8 | {{/if}} 9 | {{#if me}} 10 | {{#if notForcedMembership }} 11 |
{{endGroupMembershipTXT}} 12 | {{/if}} 13 | {{/if}} 14 |
15 | 16 | {{#if exportURL}} 17 |
{{exportGroupDetailsTXT}}
18 | {{/if}} 19 | 20 |

21 | 22 | 23 |

{{membersTXT}}{{#if memberCount}} ({{ memberCount }}){{/if}}

24 | {{#if group.ldaporg_members}} 25 | 26 | 27 | {{#if group.ldaporg_canedit}} 28 | {{#each group.ldaporg_members}} 29 | 30 | 31 | 42 | 43 | {{/each}} 44 | {{else}} 45 | {{#each group.ldaporg_members}} 46 | 47 | 48 | 49 | 50 | {{/each}} 51 | {{/if}} 52 | 53 |
{{ ldapcontacts_name }} {{#if ldaporg_admin}}{{/if}} 32 | 33 | 41 |
{{ ldapcontacts_name }} {{#if ldaporg_admin}}{{/if}} {{#if ldaporg_admin}}{{/if}}
54 | {{else}} 55 |

{{noMembersTXT}}

56 | {{/if}} 57 | {{else}} 58 |

{{selectGroupTXT}}

59 | {{/if}} 60 | -------------------------------------------------------------------------------- /appinfo/routes.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Alexander Hornig 2016 10 | */ 11 | 12 | return [ 13 | 'routes' => [ 14 | [ 'name' => 'Page#index', 'url' => '/', 'verb' => 'GET' ], 15 | [ 'name' => 'Page#loadGroups', 'url' => '/load/groups', 'verb' => 'GET' ], 16 | [ 'name' => 'Page#adminLoadGroups', 'url' => '/admin/load/groups', 'verb' => 'GET' ], 17 | [ 'name' => 'Page#loadUsers', 'url' => '/load/users', 'verb' => 'GET' ], 18 | [ 'name' => 'Page#adminLoadUsers', 'url' => '/admin/load/users', 'verb' => 'GET' ], 19 | [ 'name' => 'Page#addUserToGroup', 'url' => '/add/group/user', 'verb' => 'POST' ], 20 | [ 'name' => 'Page#removeUserFromGroup', 'url' => '/remove/group/user', 'verb' => 'POST' ], 21 | [ 'name' => 'Page#groupAddAdminUser', 'url' => '/add/group/user/admin', 'verb' => 'POST' ], 22 | [ 'name' => 'Page#groupRemoveAdminUser', 'url' => '/remove/group/user/admin', 'verb' => 'POST' ], 23 | [ 'name' => 'Page#show', 'url' => '/load/own', 'verb' => 'GET' ], 24 | [ 'name' => 'Page#canEdit', 'url' => '/canedit/{group_entry_id}', 'verb' => 'GET' ], 25 | [ 'name' => 'Page#isSuperUser', 'url' => '/superuser', 'verb' => 'GET' ], 26 | [ 'name' => 'Page#createGroup', 'url' => '/create/group', 'verb' => 'POST' ], 27 | [ 'name' => 'Page#deleteGroup', 'url' => '/delete/group/{group_entry_id}', 'verb' => 'GET' ], 28 | [ 'name' => 'Page#createUser', 'url' => '/create/user', 'verb' => 'POST' ], 29 | [ 'name' => 'Page#deleteUser', 'url' => '/delete/user/{user_entry_id}', 'verb' => 'GET' ], 30 | [ 'name' => 'Page#sendWelcomeMail', 'url' => '/welcomemail', 'verb' => 'POST' ], 31 | [ 'name' => 'Page#applyForcedGroupMemberships', 'url' => '/apply/forcedMemberships', 'verb' => 'GET' ], 32 | [ 'name' => 'Page#exportGroupMemberDetails', 'url' => '/export/{group_entry_id}', 'verb' => 'GET' ], 33 | 34 | [ 'name' => 'Settings#getSetting', 'url' => '/setting/{key}', 'verb' => 'GET' ], 35 | [ 'name' => 'Settings#updateSetting', 'url' => '/setting', 'verb' => 'POST' ], 36 | [ 'name' => 'Settings#getSettings', 'url' => '/settings', 'verb' => 'GET' ], 37 | [ 'name' => 'Settings#updateSettings', 'url' => '/settings', 'verb' => 'POST' ], 38 | [ 'name' => 'Settings#getUserValue', 'url' => '/settings/personal/{key}', 'verb' => 'GET' ], 39 | [ 'name' => 'Settings#setUserValue', 'url' => '/settings/personal', 'verb' => 'POST' ], 40 | ] 41 | ]; 42 | -------------------------------------------------------------------------------- /css/tutorial.css: -------------------------------------------------------------------------------- 1 | #tutorial-container { 2 | white-space: nowrap; 3 | position: absolute; 4 | z-index: 9999; 5 | background-color: #fff; 6 | padding: 7px 12px; 7 | background: #ccf0ff; 8 | border: 3px solid #69abf5; 9 | border-radius: 5px; 10 | } 11 | #tutorial-container:after, #tutorial-container:before { 12 | right: 100%; 13 | border: solid transparent; 14 | content: " "; 15 | height: 0; 16 | width: 0; 17 | position: absolute; 18 | pointer-events: none; 19 | } 20 | 21 | #tutorial-container:after { 22 | border-color: rgba(204, 240, 255, 0); 23 | border-right-color: #ccf0ff; 24 | } 25 | #tutorial-container:before { 26 | border-color: rgba(105, 171, 245, 0); 27 | border-right-color: #69abf5; 28 | } 29 | 30 | /*** tutorial specifi styles ***/ 31 | /* 0 */ 32 | #tutorial-container[tutorial-id="0"] { 33 | top: -3px; 34 | left: 100%; 35 | margin-left: -32px; 36 | } 37 | 38 | #tutorial-container[tutorial-id="0"]:after, #tutorial-container[tutorial-id="0"]:before { 39 | top: 23px; 40 | } 41 | 42 | #tutorial-container[tutorial-id="0"]:after { 43 | border-width: 16px; 44 | margin-top: -16.5px; 45 | } 46 | 47 | #tutorial-container[tutorial-id="0"]:before { 48 | border-width: 21px; 49 | margin-top: -21px; 50 | } 51 | 52 | /* 1 */ 53 | #tutorial-container[tutorial-id="1"] { 54 | top: 48px; 55 | left: 100%; 56 | margin-left: -55px; 57 | } 58 | 59 | #tutorial-container[tutorial-id="1"]:after, #tutorial-container[tutorial-id="1"]:before { 60 | top: 23px; 61 | } 62 | 63 | #tutorial-container[tutorial-id="1"]:after { 64 | border-width: 16px; 65 | margin-top: -16.5px; 66 | } 67 | 68 | #tutorial-container[tutorial-id="1"]:before { 69 | border-width: 21px; 70 | margin-top: -21px; 71 | } 72 | 73 | /* 2 */ 74 | #tutorial-container[tutorial-id="2"] { 75 | top: 96px; 76 | left: 100%; 77 | margin-left: -70px; 78 | } 79 | 80 | #tutorial-container[tutorial-id="2"]:after, #tutorial-container[tutorial-id="2"]:before { 81 | top: 23px; 82 | } 83 | 84 | #tutorial-container[tutorial-id="2"]:after { 85 | border-width: 16px; 86 | margin-top: -55px; 87 | margin-right: -45.5px; 88 | border-right-color: transparent; 89 | border-bottom-color: #ccf0ff; 90 | } 91 | 92 | #tutorial-container[tutorial-id="2"]:before { 93 | border-width: 21px; 94 | margin-top: -64px; 95 | margin-right: -50px; 96 | border-right-color: transparent; 97 | border-bottom-color: #69abf5; 98 | } 99 | 100 | /* 3 */ 101 | #tutorial-container[tutorial-id="3"] { 102 | top: 18px; 103 | left: 15px; 104 | } 105 | 106 | #tutorial-container[tutorial-id="3"]:after, #tutorial-container[tutorial-id="3"]:before { 107 | top: 23px; 108 | } 109 | 110 | #tutorial-container[tutorial-id="3"]:after { 111 | border-width: 16px; 112 | margin-top: -55px; 113 | margin-right: -45.5px; 114 | border-right-color: transparent; 115 | border-bottom-color: #ccf0ff; 116 | } 117 | 118 | #tutorial-container[tutorial-id="3"]:before { 119 | border-width: 21px; 120 | margin-top: -65px; 121 | margin-right: -50px; 122 | border-right-color: transparent; 123 | border-bottom-color: #69abf5; 124 | } 125 | 126 | /* 4 */ 127 | #tutorial-container[tutorial-id="4"] { 128 | top: 18px; 129 | left: 160px; 130 | } 131 | 132 | #tutorial-container[tutorial-id="4"]:after, #tutorial-container[tutorial-id="4"]:before { 133 | top: 23px; 134 | } 135 | 136 | #tutorial-container[tutorial-id="4"]:after { 137 | border-width: 16px; 138 | margin-top: -55px; 139 | margin-right: -45.5px; 140 | border-right-color: transparent; 141 | border-bottom-color: #ccf0ff; 142 | } 143 | 144 | #tutorial-container[tutorial-id="4"]:before { 145 | border-width: 21px; 146 | margin-top: -65px; 147 | margin-right: -50px; 148 | border-right-color: transparent; 149 | border-bottom-color: #69abf5; 150 | } 151 | 152 | /* 5 */ 153 | #tutorial-container[tutorial-id="5"] { 154 | top: 75px; 155 | left: 15px; 156 | } 157 | 158 | #tutorial-container[tutorial-id="5"]:after, #tutorial-container[tutorial-id="5"]:before { 159 | bottom: 0px; 160 | left: 0px; 161 | } 162 | 163 | #tutorial-container[tutorial-id="5"]:after { 164 | border-width: 16px; 165 | border-right-color: transparent; 166 | border-top-color: #ccf0ff; 167 | margin-bottom: -32px; 168 | margin-left: 24.5px; 169 | } 170 | 171 | #tutorial-container[tutorial-id="5"]:before { 172 | border-width: 21px; 173 | border-right-color: transparent; 174 | border-top-color: #69abf5; 175 | margin-bottom: -42px; 176 | margin-left: 20px; 177 | } 178 | 179 | /* 6 */ 180 | #tutorial-container[tutorial-id="6"] { 181 | top: 116px; 182 | left: 15px; 183 | } 184 | 185 | #tutorial-container[tutorial-id="6"]:after, #tutorial-container[tutorial-id="6"]:before { 186 | bottom: 0px; 187 | left: 0px; 188 | } 189 | 190 | #tutorial-container[tutorial-id="6"]:after { 191 | border-width: 16px; 192 | border-right-color: transparent; 193 | border-bottom-color: #ccf0ff; 194 | margin-bottom: 74px; 195 | margin-left: 24.5px; 196 | } 197 | 198 | #tutorial-container[tutorial-id="6"]:before { 199 | border-width: 21px; 200 | border-right-color: transparent; 201 | border-bottom-color: #69abf5; 202 | margin-bottom: 74px; 203 | margin-left: 20px; 204 | } -------------------------------------------------------------------------------- /css/settings.css: -------------------------------------------------------------------------------- 1 | /* general */ 2 | #LdapOrgSettings table > tbody > tr > td:first-child { 3 | padding: 0 10px 0 5px; 4 | } 5 | /* end of general */ 6 | 7 | 8 | /* content */ 9 | #ldaporg-existing-users { 10 | display: inline-block; 11 | max-height: 300px; 12 | overflow: auto; 13 | border: 1px #aaa solid; 14 | } 15 | 16 | #ldaporg-existing-users > .user { 17 | position: relative; 18 | padding: 5px 30px 5px 5px; 19 | } 20 | 21 | #ldaporg-existing-users > .user:focus, #ldaporg-existing-users > .user:hover, #ldaporg-existing-users > .user:active { 22 | background-color: #ddd; 23 | } 24 | 25 | #ldaporg-existing-users > .user > span.icon { 26 | display: block; 27 | position: absolute; 28 | top: 7px; 29 | right: 7px; 30 | cursor: pointer; 31 | } 32 | 33 | #ldaporg-user-content { 34 | display: inline-block; 35 | vertical-align: top; 36 | padding: 0 10px 0 20px; 37 | } 38 | 39 | #ldaporg-user-content .delete-user { 40 | transition: background-color 0.3s, color 0.3s; 41 | } 42 | 43 | #ldaporg-user-content .delete-user:focus, #ldaporg-user-content .delete-user:hover, #ldaporg-user-content .delete-user:active { 44 | background-color: #E57B7B; 45 | color: white; 46 | } 47 | 48 | #ldaporg-edit-settings input[type="text"], #ldaporg-edit-settings input[type="url"], #ldaporg-edit-settings input[type="email"], 49 | #ldaporg-edit-settings textarea { 50 | width: 100%; 51 | } 52 | 53 | #ldaporg-edit-settings #ldaporg_welcome_mail_message { 54 | min-height: 150px; 55 | min-width: 300px; 56 | } 57 | 58 | #ldaporg-edit-settings .pwd_reset_url td:first-child label, #ldaporg-edit-settings .pwd_reset_url td:last-child { 59 | padding-left: 30px; 60 | } 61 | 62 | #ldaporg-edit-settings tr:not(.pwd_reset_url) td:first-child label { 63 | padding-right: 30px; 64 | } 65 | 66 | #ldaporg-edit-settings .checkbox-wrapper-wrapper { 67 | display: flex; 68 | flex-wrap: wrap; 69 | } 70 | 71 | #ldaporg-edit-settings .checkbox-wrapper { 72 | padding-right: 20px; 73 | } 74 | 75 | #ldaporg-edit-settings .checkbox-wrapper > input[type="checkbox"] { 76 | vertical-align: middle; 77 | margin: 3px 3px 4px 0px; 78 | } 79 | 80 | #ldaporg-force-group-membership .container { 81 | margin-top: 10px; 82 | margin-bottom: 10px; 83 | padding: 5px; 84 | border: 1px solid grey; 85 | border-radius: 10px; 86 | } 87 | /* end of content */ 88 | 89 | /* messages */ 90 | #LdapOrgSettings .msg { 91 | border-radius: 3px; 92 | background-color: #f3c800; 93 | padding: 5px; 94 | display: none; 95 | } 96 | 97 | #LdapOrgSettings .msg.success { 98 | color: #fff; 99 | background-color: #47a447; 100 | } 101 | #LdapOrgSettings .msg.error { 102 | color: #fff; 103 | background-color: #d2322d; 104 | } 105 | 106 | #LdapOrgSettings .msg-container { 107 | height: 25px; 108 | } 109 | /* end of messages */ 110 | 111 | /* search */ 112 | .force-group-membership { 113 | display: inline-block; 114 | margin: 5px; 115 | } 116 | 117 | .force-group-membership > * { 118 | background-color: rgba(240, 240, 240, 0.9); 119 | display: inline-block; 120 | height: 23px; 121 | padding: 1px 7px 0; 122 | } 123 | 124 | .force-group-membership > .name { 125 | display: inline-block; 126 | border-right: 1px solid grey; 127 | border-radius: 5px 0 0 5px; 128 | } 129 | 130 | .force-group-membership > .remove { 131 | border-left: 1px solid grey; 132 | font-weight: bold; 133 | z-index: 10; 134 | cursor: pointer; 135 | border-radius: 0 5px 5px 0; 136 | } 137 | 138 | .force-group-membership > .remove:focus, .force-group-membership > .remove:hover, .force-group-membership > .remove:active { 139 | background-color: rgba(201, 201, 201, 1); 140 | } 141 | 142 | #LdapOrgSettings .search-container { 143 | position: relative; 144 | } 145 | 146 | #LdapOrgSettings span.search { 147 | position: relative; 148 | } 149 | 150 | #LdapOrgSettings .search-suggestions { 151 | display: inline-block; 152 | position: absolute; 153 | top: 32px; 154 | left: 2px; 155 | z-index: 2; 156 | } 157 | 158 | #LdapOrgSettings .search-suggestions > .suggestion { 159 | z-index: 2; 160 | cursor: pointer; 161 | background-color: rgba(240, 240, 240, 1); 162 | padding: 3px 10px; 163 | } 164 | 165 | #LdapOrgSettings .search-suggestions > .suggestion:focus, #LdapOrgSettings .search-suggestions > .suggestion:hover, #LdapOrgSettings .search-suggestions > .suggestion:active { 166 | background-color: rgba(201, 201, 201, 1); 167 | } 168 | 169 | #LdapOrgSettings .search-suggestions > .suggestion:first-child { 170 | 171 | } 172 | 173 | #LdapOrgSettings .search-suggestions > .suggestion:last-child { 174 | border-radius: 0 0 4px 4px; 175 | } 176 | 177 | #LdapOrgSettings input[type="search"] { 178 | background: transparent url('../img/search.svg?v=1') no-repeat 5px center; 179 | padding: 5px 25px; 180 | transition: width 0.5s; 181 | min-width: 250px; 182 | } 183 | 184 | #LdapOrgSettings input[type="search"] + .abort { 185 | opacity: 0; 186 | transition: opacity 0.5s; 187 | background: transparent url('../img/remove.svg?v=1') no-repeat center; 188 | background-size: 100%; 189 | width: 0; 190 | height: 20px; 191 | position: absolute; 192 | top: 18px; 193 | right: 12px; 194 | } 195 | 196 | #LdapOrgSettings input[type="search"].searching + .abort { 197 | opacity: 0.5; 198 | width: 20px; 199 | cursor: pointer; 200 | margin-top: -20px; 201 | } 202 | 203 | #LdapOrgSettings input[type="search"].searching + .abort:hover, #LdapOrgSettings input[type="search"].searching + .abort:focus, #LdapOrgSettings input[type="search"].searching + .abort:active { 204 | opacity: 0.8; 205 | } 206 | /* end of search */ -------------------------------------------------------------------------------- /lib/Controller/SettingsController.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Hornig Software 2017 10 | */ 11 | 12 | namespace OCA\LdapOrg\Controller; 13 | 14 | use OCP\IL10N; 15 | use OCP\IRequest; 16 | use OCP\IConfig; 17 | use OCP\AppFramework\Http\DataResponse; 18 | use OCP\AppFramework\Controller; 19 | 20 | class SettingsController extends Controller { 21 | protected $AppName;private $uid; 22 | protected $array_settings = [ 'superuser_groups', 'forced_group_memberships' ]; 23 | /** @var IL10N */ 24 | private $l; 25 | /** @var IConfig */ 26 | private $config; 27 | // default values 28 | private $default; 29 | private $user_default; 30 | 31 | /** 32 | * @param string $AppName 33 | * @param IRequest $request 34 | * @param IL10N $l10n 35 | * @param IConfig $config 36 | * @param string $UserId 37 | */ 38 | public function __construct( string $AppName, IRequest $request, IL10N $l10n, IConfig $config, string $UserId ) { 39 | // check we have a logged in user 40 | \OCP\User::checkLoggedIn(); 41 | parent::__construct( $AppName, $request ); 42 | // set class variables 43 | $this->AppName = $AppName; 44 | $this->config = $config; 45 | // load translation files 46 | $this->l = $l10n; 47 | // get the current users id 48 | $this->uid = $UserId; 49 | // fill default values 50 | $this->default = [ 51 | 'superuser_groups' => '[]', 52 | 'user_gidnumber' => 501, 53 | 'pwd_reset_url_active' => false, 54 | 'pwd_reset_url' => '', 55 | 'pwd_reset_url_attr' => 'login', 56 | 'pwd_reset_url_attr_ldap_attr' => 'mail', 57 | 'pwd_reset_tag' => '', 58 | 'welcome_mail_subject' => $this->l->t( 'Welcome to Nextcloud' ), 59 | 'welcome_mail_from_adress' => 'info@example.com', 60 | 'welcome_mail_from_name' => 'Nextcloud', 61 | 'welcome_mail_message' => $this->l->t( 'Welcome to Nextcloud! We hope you enjoy your time here.' ), 62 | 'forced_group_memberships' => '[]', 63 | 'csv_seperator' => ';', 64 | 'mail_attribute' => 'mail', 65 | 'firstname_attribute' => 'givenname', 66 | 'lastname_attribtue' => 'sn', 67 | ]; 68 | $this->user_default = [ 69 | 'tutorial_state' => 0, 70 | ]; 71 | } 72 | 73 | /** 74 | * returns the value for the given general setting 75 | * 76 | * @param string $key 77 | */ 78 | public function getSetting( string $key ) { 79 | // check if this is a valid setting 80 | if( !isset( $this->default[ $key ] ) ) return false; 81 | $setting = $this->config->getAppValue( $this->appName, $key, $this->default[ $key ] ); 82 | // if this is an array setting, convert it 83 | if( in_array( $key, $this->array_settings ) ) $setting = json_decode( $setting ); 84 | // return the setting 85 | return $setting; 86 | } 87 | 88 | /** 89 | * returns all settings from this app 90 | * 91 | * @NoAdminRequired 92 | */ 93 | public function getSettings() { 94 | // output buffer 95 | $data = array(); 96 | // go through every existing setting 97 | foreach( $this->default as $key => $v ) { 98 | // get the settings value 99 | $data[ $key ] = $this->getSetting( $key ); 100 | } 101 | // return the buffered data 102 | return new DataResponse( $data ); 103 | } 104 | 105 | /* 106 | * updates the given setting 107 | * 108 | * @param string $key 109 | * @param mixed $value 110 | */ 111 | public function updateSetting( string $key, $value ) { 112 | // check if the setting is an actual setting this app has 113 | if( !isset( $this->default[ $key ] ) ) return false; 114 | // if this is an array setting, convert it 115 | if( in_array( $key, $this->array_settings ) ) $value = json_encode( $value ); 116 | // save the setting 117 | return !$this->config->setAppValue( $this->appName, $key, $value ); 118 | } 119 | 120 | /** 121 | * returns all settings from this app 122 | * 123 | * @param string $settings 124 | */ 125 | public function updateSettings( string $settings ) { 126 | // parse the serialized form 127 | parse_str( urldecode( $settings ), $array ); 128 | $settings = $array; 129 | 130 | $success = true; 131 | // go through every setting and update it 132 | foreach( $settings as $key => $value ) { 133 | // update the setting 134 | $success &= $this->updateSetting( $key, $value ); 135 | } 136 | 137 | // return message 138 | if( $success ) return new DataResponse( [ 'data' => [ 'message' => $this->l->t( 'Settings saved' ) ], 'status' => 'success'] ); 139 | else return new DataResponse( [ 'data' => [ 'message' => $this->l->t( 'Something went wrong while saving the settings. Please try again.' ) ], 'status' => 'error' ] ); 140 | } 141 | 142 | /** 143 | * gets the value for the given setting 144 | * 145 | * @param string $key 146 | * @NoAdminRequired 147 | */ 148 | public function getUserValue( string $key ) { 149 | // check if this is a valid setting 150 | if( !isset( $this->user_default[ $key ] ) ) return false; 151 | $value = $this->config->getUserValue( $this->uid, $this->appName, $key, $this->user_default[ $key ] ); 152 | // if this is an array setting, convert it 153 | if( in_array( $key, $this->array_settings ) ) $value = json_decode( $value ); 154 | return $value; 155 | } 156 | 157 | /** 158 | * saves the given setting an returns a DataResponse 159 | * 160 | * @param string $key 161 | * @param mixed $value 162 | * @NoAdminRequired 163 | */ 164 | public function setUserValue( string $key, $value ) { 165 | // check if this is a valid setting 166 | if( !isset( $this->user_default[ $key ] ) ) return false; 167 | // if this is an array setting, convert it 168 | if( in_array( $key, $this->array_settings ) ) $value = json_encode( $value ); 169 | // save the value 170 | return $this->config->setUserValue( $this->uid, $this->appName, $key, $value ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /l10n/de.json: -------------------------------------------------------------------------------- 1 | { "translations": { 2 | "Groups" : "Gruppen", 3 | "Permission denied" : "Zugriff verweigert", 4 | "User is now an admin" : "Benutzer ist jetzt ein Admin", 5 | "Making user admin failed" : "Benutzer zum Admin machen fehlgeschlagen", 6 | "User is not an admin anymore" : "Benutzer ist kein Admin mehr", 7 | "Removing admin privileges failed" : "Admin Privilegien entfernen fehlgeschlagen", 8 | "User successfully added" : "Benutzer erfolgreich hinzugefügt", 9 | "Adding the user failed" : "Benutzer hinzufügen fehlgeschlagen", 10 | "You are not a member of the group anymore" : "Sie sind kein Mitglied der Gruppe mehr", 11 | "Leaving the group failed" : "Gruppe verlassen fehlgeschlagen", 12 | "User successfully removed" : "Benutzer erfolgreich entfernt", 13 | "Removing the user failed" : "Benutzer entfernen fehlgeschlagen", 14 | "Removing users from this group is not possible" : "Benutzer von dieser Gruppe zu entfernen ist nicht erlaubt", 15 | "Leaving this group is not possible" : "Diese Gruppe zu verlassen ist nicht möglich", 16 | "Group name can't be empty" : "Gruppenname darf nicht leer sein", 17 | "A group with the same name already exists" : "Eine Gruppe mit dem selben Namen existiert bereits", 18 | "Group successfully created" : "Gruppe wurde erfolgreich erstellt", 19 | "Creating the group failed" : "Gruppe erstellen fehlgeschlagen", 20 | "A superuser group can't be deleted" : "Eine Superuser Gruppe kann nicht gelöscht werden", 21 | "Removing the group failed" : "Entfernen der Gruppe fehlgeschlagen", 22 | "Group successfully removed" : "Gruppe erfolgreich entfernt", 23 | "User successfully deleted" : "Benutzer erfolgreich gelöscht", 24 | "Deleting the user failed" : "Benutzer löschen fehlgeschlagen", 25 | "No value can be empty" : "Kein Wert darf leer sein", 26 | "No value can be longer than 100 characters" : "Keiner der eingegebenen Werte darf länger als 100 Zeichen sein", 27 | "Another user with the same email adress already exists" : "Ein Benutzer mit der selben E-Mail Adresse existiert bereits", 28 | "User successfully created" : "Benutzer erfolgreich erstellt", 29 | "Creating user failed" : "Benutzer erstellen fehlgeschlagen", 30 | "Welcome Mail has been send" : "Willkommens E-Mail wurde gesendet", 31 | "Sending the welcome mail failed" : "Senden der Willkommens E-Mail ist fehlgeschlagen", 32 | "Applying forced group memberships failed" : "Verpflichtende Gruppenmitgliedschaften anwenden war nicht erfolgreich", 33 | "Applied forced group memberships" : "Verpflichtende Gruppenmitgliedschaften erfolgreich angewendet", 34 | "Group not found or permission denied" : "Gruppe konnte nicht gefunden werden oder der Zugriff wurde verweigert", 35 | "Welcome to Nextcloud" : "Willkommen bei Nextcloud", 36 | "Welcome to Nextcloud! We hope you enjoy your time here." : "Willkommen bei Nextcloud! Wir hoffen Sie genießen die Zeit hier.", 37 | "Settings saved" : "Einstellungen gespeichert", 38 | "Something went wrong while saving the settings. Please try again." : "Die Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.", 39 | "Add Group" : "Gruppe erstellen", 40 | "Delete Group" : "Gruppe löschen", 41 | "You are not a member of any group" : "Sie sind noch kein Mitglied einer Gruppe", 42 | "Add Member" : "Mitglied hinzufügen", 43 | "End Group Membership" : "Gruppen Mitgliedschaft beenden", 44 | "Export group member details" : "Details der Gruppenmitglieder exportieren", 45 | "Members" : "Mitglieder", 46 | "Group Admin" : "Gruppen Admin", 47 | "Remove Admin Privileges" : "Admin Privilegien entfernen", 48 | "Make Admin" : "Zum Admin machen", 49 | "Remove" : "Entfernen", 50 | "There are no members in this group yet" : "Diese Gruppe hat noch keine Mitglieder", 51 | "Select a group from the list to view details" : "Wähle ein Gruppe aus der Liste um Details anzuzeigen", 52 | "Group Name" : "Gruppenname", 53 | "Do you really want to remove the group \"{{ group.ldapcontacts_name }}\"?" : "Wollen Sie die Gruppe \"{{ group.ldapcontacts_name }}\" wirklich löschen?", 54 | "Yes" : "Ja", 55 | "No" : "Nein", 56 | "Got it" : "Alles Klar", 57 | "For creating a group click here" : "Klicke hier, um eine Gruppe zu erstellen", 58 | "Be careful! This button will delete a group" : "Achtung! Hiermit löschst du eine Gruppe", 59 | "Search for a user here to add him to this group" : "Suche nach einem Benutzer, um ihn zu der Gruppe hinzuzufügen", 60 | "Here you can leave this group" : "Hier kannst du die Gruppe verlassen", 61 | "All members of this group are listed here" : "Alle Mitglieder dieser Gruppe findest du hier", 62 | "Use this to export all information for every member of this group" : "Damit kannst du alle Informationen über alle Mitglieder dieser Gruppe exportieren", 63 | "LDAP Org Settings" : "LDAP Org Einstellungen", 64 | "Superuser Group" : "Superuser Gruppe", 65 | "Use Password Reset URL" : "Passwort zurücksetzen URL benutzen", 66 | "Password Reset URL" : "Passwort zurücksetzen URL", 67 | "Password Reset Attribute" : "Passwort zurücksetzen Attribut", 68 | "Corresponding LDAP Attribute" : "Zugehöriges LDAP Attribut", 69 | "LDAP Attribute" : "LDAP Attribut", 70 | "Link tag to be replaced" : "Link-Kennzeichnung, die Ersetzt wird", 71 | "Welcome Mail Subject" : "Willkommens Mail Betreff", 72 | "Welcome Mail FROM" : "Willkommens Mail VON", 73 | "E-Mail Address" : "E-Mail Adresse", 74 | "Welcome Mail FROM Name" : "Willkommens Mail VON Name", 75 | "Name" : "Name", 76 | "Welcome Mail Message" : "Willkommens Mail Nachricht", 77 | "Message" : "Nachricht", 78 | "Save" : "Speichern", 79 | "add mandatory group" : "verpflichtende Gruppe hinzufügen", 80 | "Users aren't forced to be a member of any group" : "Die Benutzer sind nicht gezwungen Mitglied einer bestimmten Gruppe zu sein", 81 | "Mandatory group memberships" : "Verpflichtende Gruppenmitgliedschaft", 82 | "Apply forced group memberships to all users" : "Verpflichtende Gruppenmitgliedschaften auf alle Benutzer anwenden", 83 | "LDAP Users" : "LDAP Benutzer", 84 | "Create User" : "Benutzer erstellen", 85 | "Select" : "Auswählen", 86 | "Resend Welcome Mail" : "Willkommens E-Mail erneut senden", 87 | "Delete User" : "Benutzer löschen", 88 | "Create a user" : "Einen Benutzer erstellen", 89 | "Firstname" : "Vorname", 90 | "Lastname" : "Nachname", 91 | "Create" : "Erstellen", 92 | "Abort" : "Abbrechen", 93 | "Do you really want to delete the user {{ user.ldapcontacts_name }}?" : "Wollen Sie den Benutzer \"{{ userldapcontacts_name }}\" wirklich löschen?" 94 | },"pluralForm" :"nplurals=2; plural=(n != 1);" 95 | } -------------------------------------------------------------------------------- /l10n/de_DE.json: -------------------------------------------------------------------------------- 1 | { "translations": { 2 | "Groups" : "Gruppen", 3 | "Permission denied" : "Zugriff verweigert", 4 | "User is now an admin" : "Benutzer ist jetzt ein Admin", 5 | "Making user admin failed" : "Benutzer zum Admin machen fehlgeschlagen", 6 | "User is not an admin anymore" : "Benutzer ist kein Admin mehr", 7 | "Removing admin privileges failed" : "Admin Privilegien entfernen fehlgeschlagen", 8 | "User successfully added" : "Benutzer erfolgreich hinzugefügt", 9 | "Adding the user failed" : "Benutzer hinzufügen fehlgeschlagen", 10 | "You are not a member of the group anymore" : "Sie sind kein Mitglied der Gruppe mehr", 11 | "Leaving the group failed" : "Gruppe verlassen fehlgeschlagen", 12 | "User successfully removed" : "Benutzer erfolgreich entfernt", 13 | "Removing the user failed" : "Benutzer entfernen fehlgeschlagen", 14 | "Removing users from this group is not possible" : "Benutzer von dieser Gruppe zu entfernen ist nicht erlaubt", 15 | "Leaving this group is not possible" : "Diese Gruppe zu verlassen ist nicht möglich", 16 | "Group name can't be empty" : "Gruppenname darf nicht leer sein", 17 | "A group with the same name already exists" : "Eine Gruppe mit dem selben Namen existiert bereits", 18 | "Group successfully created" : "Gruppe wurde erfolgreich erstellt", 19 | "Creating the group failed" : "Gruppe erstellen fehlgeschlagen", 20 | "A superuser group can't be deleted" : "Eine Superuser Gruppe kann nicht gelöscht werden", 21 | "Removing the group failed" : "Entfernen der Gruppe fehlgeschlagen", 22 | "Group successfully removed" : "Gruppe erfolgreich entfernt", 23 | "User successfully deleted" : "Benutzer erfolgreich gelöscht", 24 | "Deleting the user failed" : "Benutzer löschen fehlgeschlagen", 25 | "No value can be empty" : "Kein Wert darf leer sein", 26 | "No value can be longer than 100 characters" : "Keiner der eingegebenen Werte darf länger als 100 Zeichen sein", 27 | "Another user with the same email adress already exists" : "Ein Benutzer mit der selben E-Mail Adresse existiert bereits", 28 | "User successfully created" : "Benutzer erfolgreich erstellt", 29 | "Creating user failed" : "Benutzer erstellen fehlgeschlagen", 30 | "Welcome Mail has been send" : "Willkommens E-Mail wurde gesendet", 31 | "Sending the welcome mail failed" : "Senden der Willkommens E-Mail ist fehlgeschlagen", 32 | "Applying forced group memberships failed" : "Verpflichtende Gruppenmitgliedschaften anwenden war nicht erfolgreich", 33 | "Applied forced group memberships" : "Verpflichtende Gruppenmitgliedschaften erfolgreich angewendet", 34 | "Group not found or permission denied" : "Gruppe konnte nicht gefunden werden oder der Zugriff wurde verweigert", 35 | "Welcome to Nextcloud" : "Willkommen bei Nextcloud", 36 | "Welcome to Nextcloud! We hope you enjoy your time here." : "Willkommen bei Nextcloud! Wir hoffen Sie genießen die Zeit hier.", 37 | "Settings saved" : "Einstellungen gespeichert", 38 | "Something went wrong while saving the settings. Please try again." : "Die Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.", 39 | "Add Group" : "Gruppe erstellen", 40 | "Delete Group" : "Gruppe löschen", 41 | "You are not a member of any group" : "Sie sind noch kein Mitglied einer Gruppe", 42 | "Add Member" : "Mitglied hinzufügen", 43 | "End Group Membership" : "Gruppen Mitgliedschaft beenden", 44 | "Export group member details" : "Details der Gruppenmitglieder exportieren", 45 | "Members" : "Mitglieder", 46 | "Group Admin" : "Gruppen Admin", 47 | "Remove Admin Privileges" : "Admin Privilegien entfernen", 48 | "Make Admin" : "Zum Admin machen", 49 | "Remove" : "Entfernen", 50 | "There are no members in this group yet" : "Diese Gruppe hat noch keine Mitglieder", 51 | "Select a group from the list to view details" : "Wähle ein Gruppe aus der Liste um Details anzuzeigen", 52 | "Group Name" : "Gruppenname", 53 | "Do you really want to remove the group \"{{ group.ldapcontacts_name }}\"?" : "Wollen Sie die Gruppe \"{{ group.ldapcontacts_name }}\" wirklich löschen?", 54 | "Yes" : "Ja", 55 | "No" : "Nein", 56 | "Got it" : "Alles Klar", 57 | "For creating a group click here" : "Klicke hier, um eine Gruppe zu erstellen", 58 | "Be careful! This button will delete a group" : "Achtung! Hiermit löschst du eine Gruppe", 59 | "Search for a user here to add him to this group" : "Suche nach einem Benutzer, um ihn zu der Gruppe hinzuzufügen", 60 | "Here you can leave this group" : "Hier kannst du die Gruppe verlassen", 61 | "All members of this group are listed here" : "Alle Mitglieder dieser Gruppe findest du hier", 62 | "Use this to export all information for every member of this group" : "Damit kannst du alle Informationen über alle Mitglieder dieser Gruppe exportieren", 63 | "LDAP Org Settings" : "LDAP Org Einstellungen", 64 | "Superuser Group" : "Superuser Gruppe", 65 | "Use Password Reset URL" : "Passwort zurücksetzen URL benutzen", 66 | "Password Reset URL" : "Passwort zurücksetzen URL", 67 | "Password Reset Attribute" : "Passwort zurücksetzen Attribut", 68 | "Corresponding LDAP Attribute" : "Zugehöriges LDAP Attribut", 69 | "LDAP Attribute" : "LDAP Attribut", 70 | "Link tag to be replaced" : "Link-Kennzeichnung, die Ersetzt wird", 71 | "Welcome Mail Subject" : "Willkommens Mail Betreff", 72 | "Welcome Mail FROM" : "Willkommens Mail VON", 73 | "E-Mail Address" : "E-Mail Adresse", 74 | "Welcome Mail FROM Name" : "Willkommens Mail VON Name", 75 | "Name" : "Name", 76 | "Welcome Mail Message" : "Willkommens Mail Nachricht", 77 | "Message" : "Nachricht", 78 | "Save" : "Speichern", 79 | "add mandatory group" : "verpflichtende Gruppe hinzufügen", 80 | "Users aren't forced to be a member of any group" : "Die Benutzer sind nicht gezwungen Mitglied einer bestimmten Gruppe zu sein", 81 | "Mandatory group memberships" : "Verpflichtende Gruppenmitgliedschaft", 82 | "Apply forced group memberships to all users" : "Verpflichtende Gruppenmitgliedschaften auf alle Benutzer anwenden", 83 | "LDAP Users" : "LDAP Benutzer", 84 | "Create User" : "Benutzer erstellen", 85 | "Select" : "Auswählen", 86 | "Resend Welcome Mail" : "Willkommens E-Mail erneut senden", 87 | "Delete User" : "Benutzer löschen", 88 | "Create a user" : "Einen Benutzer erstellen", 89 | "Firstname" : "Vorname", 90 | "Lastname" : "Nachname", 91 | "Create" : "Erstellen", 92 | "Abort" : "Abbrechen", 93 | "Do you really want to delete the user {{ user.ldapcontacts_name }}?" : "Wollen Sie den Benutzer \"{{ userldapcontacts_name }}\" wirklich löschen?" 94 | },"pluralForm" :"nplurals=2; plural=(n != 1);" 95 | } -------------------------------------------------------------------------------- /l10n/de.js: -------------------------------------------------------------------------------- 1 | OC.L10N.register( 2 | "ldaporg", 3 | { 4 | "Groups" : "Gruppen", 5 | "Permission denied" : "Zugriff verweigert", 6 | "User is now an admin" : "Benutzer ist jetzt ein Admin", 7 | "Making user admin failed" : "Benutzer zum Admin machen fehlgeschlagen", 8 | "User is not an admin anymore" : "Benutzer ist kein Admin mehr", 9 | "Removing admin privileges failed" : "Admin Privilegien entfernen fehlgeschlagen", 10 | "User successfully added" : "Benutzer erfolgreich hinzugefügt", 11 | "Adding the user failed" : "Benutzer hinzufügen fehlgeschlagen", 12 | "You are not a member of the group anymore" : "Sie sind kein Mitglied der Gruppe mehr", 13 | "Leaving the group failed" : "Gruppe verlassen fehlgeschlagen", 14 | "User successfully removed" : "Benutzer erfolgreich entfernt", 15 | "Removing the user failed" : "Benutzer entfernen fehlgeschlagen", 16 | "Removing users from this group is not possible" : "Benutzer von dieser Gruppe zu entfernen ist nicht erlaubt", 17 | "Leaving this group is not possible" : "Diese Gruppe zu verlassen ist nicht möglich", 18 | "Group name can't be empty" : "Gruppenname darf nicht leer sein", 19 | "A group with the same name already exists" : "Eine Gruppe mit dem selben Namen existiert bereits", 20 | "Group successfully created" : "Gruppe wurde erfolgreich erstellt", 21 | "Creating the group failed" : "Gruppe erstellen fehlgeschlagen", 22 | "A superuser group can't be deleted" : "Eine Superuser Gruppe kann nicht gelöscht werden", 23 | "Removing the group failed" : "Entfernen der Gruppe fehlgeschlagen", 24 | "Group successfully removed" : "Gruppe erfolgreich entfernt", 25 | "User successfully deleted" : "Benutzer erfolgreich gelöscht", 26 | "Deleting the user failed" : "Benutzer löschen fehlgeschlagen", 27 | "No value can be empty" : "Kein Wert darf leer sein", 28 | "No value can be longer than 100 characters" : "Keiner der eingegebenen Werte darf länger als 100 Zeichen sein", 29 | "Another user with the same email adress already exists" : "Ein Benutzer mit der selben E-Mail Adresse existiert bereits", 30 | "User successfully created" : "Benutzer erfolgreich erstellt", 31 | "Creating user failed" : "Benutzer erstellen fehlgeschlagen", 32 | "Welcome Mail has been send" : "Willkommens E-Mail wurde gesendet", 33 | "Sending the welcome mail failed" : "Senden der Willkommens E-Mail ist fehlgeschlagen", 34 | "Applying forced group memberships failed" : "Verpflichtende Gruppenmitgliedschaften anwenden war nicht erfolgreich", 35 | "Applied forced group memberships" : "Verpflichtende Gruppenmitgliedschaften erfolgreich angewendet", 36 | "Group not found or permission denied" : "Gruppe konnte nicht gefunden werden oder der Zugriff wurde verweigert", 37 | "Welcome to Nextcloud" : "Willkommen bei Nextcloud", 38 | "Welcome to Nextcloud! We hope you enjoy your time here." : "Willkommen bei Nextcloud! Wir hoffen Sie genießen die Zeit hier.", 39 | "Settings saved" : "Einstellungen gespeichert", 40 | "Something went wrong while saving the settings. Please try again." : "Die Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.", 41 | "Add Group" : "Gruppe erstellen", 42 | "Delete Group" : "Gruppe löschen", 43 | "You are not a member of any group" : "Sie sind noch kein Mitglied einer Gruppe", 44 | "Add Member" : "Mitglied hinzufügen", 45 | "End Group Membership" : "Gruppen Mitgliedschaft beenden", 46 | "Export group member details" : "Details der Gruppenmitglieder exportieren", 47 | "Members" : "Mitglieder", 48 | "Group Admin" : "Gruppen Admin", 49 | "Remove Admin Privileges" : "Admin Privilegien entfernen", 50 | "Make Admin" : "Zum Admin machen", 51 | "Remove" : "Entfernen", 52 | "There are no members in this group yet" : "Diese Gruppe hat noch keine Mitglieder", 53 | "Select a group from the list to view details" : "Wähle ein Gruppe aus der Liste um Details anzuzeigen", 54 | "Group Name" : "Gruppenname", 55 | "Do you really want to remove the group \"{{ group.ldapcontacts_name }}\"?" : "Wollen Sie die Gruppe \"{{ group.ldapcontacts_name }}\" wirklich löschen?", 56 | "Yes" : "Ja", 57 | "No" : "Nein", 58 | "Got it" : "Alles Klar", 59 | "For creating a group click here" : "Klicke hier, um eine Gruppe zu erstellen", 60 | "Be careful! This button will delete a group" : "Achtung! Hiermit löschst du eine Gruppe", 61 | "Search for a user here to add him to this group" : "Suche nach einem Benutzer, um ihn zu der Gruppe hinzuzufügen", 62 | "Here you can leave this group" : "Hier kannst du die Gruppe verlassen", 63 | "All members of this group are listed here" : "Alle Mitglieder dieser Gruppe findest du hier", 64 | "Use this to export all information for every member of this group" : "Damit kannst du alle Informationen über alle Mitglieder dieser Gruppe exportieren", 65 | "LDAP Org Settings" : "LDAP Org Einstellungen", 66 | "Superuser Group" : "Superuser Gruppe", 67 | "Use Password Reset URL" : "Passwort zurücksetzen URL benutzen", 68 | "Password Reset URL" : "Passwort zurücksetzen URL", 69 | "Password Reset Attribute" : "Passwort zurücksetzen Attribut", 70 | "Corresponding LDAP Attribute" : "Zugehöriges LDAP Attribut", 71 | "LDAP Attribute" : "LDAP Attribut", 72 | "Link tag to be replaced" : "Link-Kennzeichnung, die Ersetzt wird", 73 | "Welcome Mail Subject" : "Willkommens Mail Betreff", 74 | "Welcome Mail FROM" : "Willkommens Mail VON", 75 | "E-Mail Address" : "E-Mail Adresse", 76 | "Welcome Mail FROM Name" : "Willkommens Mail VON Name", 77 | "Name" : "Name", 78 | "Welcome Mail Message" : "Willkommens Mail Nachricht", 79 | "Message" : "Nachricht", 80 | "Save" : "Speichern", 81 | "add mandatory group" : "verpflichtende Gruppe hinzufügen", 82 | "Users aren't forced to be a member of any group" : "Die Benutzer sind nicht gezwungen Mitglied einer bestimmten Gruppe zu sein", 83 | "Mandatory group memberships" : "Verpflichtende Gruppenmitgliedschaft", 84 | "Apply forced group memberships to all users" : "Verpflichtende Gruppenmitgliedschaften auf alle Benutzer anwenden", 85 | "LDAP Users" : "LDAP Benutzer", 86 | "Create User" : "Benutzer erstellen", 87 | "Select" : "Auswählen", 88 | "Resend Welcome Mail" : "Willkommens E-Mail erneut senden", 89 | "Delete User" : "Benutzer löschen", 90 | "Create a user" : "Einen Benutzer erstellen", 91 | "Firstname" : "Vorname", 92 | "Lastname" : "Nachname", 93 | "Create" : "Erstellen", 94 | "Abort" : "Abbrechen", 95 | "Do you really want to delete the user {{ user.ldapcontacts_name }}?" : "Wollen Sie den Benutzer \"{{ userldapcontacts_name }}\" wirklich löschen?" 96 | }, 97 | "nplurals=2; plural=(n != 1);"); -------------------------------------------------------------------------------- /l10n/de_DE.js: -------------------------------------------------------------------------------- 1 | OC.L10N.register( 2 | "ldaporg", 3 | { 4 | "Groups" : "Gruppen", 5 | "Permission denied" : "Zugriff verweigert", 6 | "User is now an admin" : "Benutzer ist jetzt ein Admin", 7 | "Making user admin failed" : "Benutzer zum Admin machen fehlgeschlagen", 8 | "User is not an admin anymore" : "Benutzer ist kein Admin mehr", 9 | "Removing admin privileges failed" : "Admin Privilegien entfernen fehlgeschlagen", 10 | "User successfully added" : "Benutzer erfolgreich hinzugefügt", 11 | "Adding the user failed" : "Benutzer hinzufügen fehlgeschlagen", 12 | "You are not a member of the group anymore" : "Sie sind kein Mitglied der Gruppe mehr", 13 | "Leaving the group failed" : "Gruppe verlassen fehlgeschlagen", 14 | "User successfully removed" : "Benutzer erfolgreich entfernt", 15 | "Removing the user failed" : "Benutzer entfernen fehlgeschlagen", 16 | "Removing users from this group is not possible" : "Benutzer von dieser Gruppe zu entfernen ist nicht erlaubt", 17 | "Leaving this group is not possible" : "Diese Gruppe zu verlassen ist nicht möglich", 18 | "Group name can't be empty" : "Gruppenname darf nicht leer sein", 19 | "A group with the same name already exists" : "Eine Gruppe mit dem selben Namen existiert bereits", 20 | "Group successfully created" : "Gruppe wurde erfolgreich erstellt", 21 | "Creating the group failed" : "Gruppe erstellen fehlgeschlagen", 22 | "A superuser group can't be deleted" : "Eine Superuser Gruppe kann nicht gelöscht werden", 23 | "Removing the group failed" : "Entfernen der Gruppe fehlgeschlagen", 24 | "Group successfully removed" : "Gruppe erfolgreich entfernt", 25 | "User successfully deleted" : "Benutzer erfolgreich gelöscht", 26 | "Deleting the user failed" : "Benutzer löschen fehlgeschlagen", 27 | "No value can be empty" : "Kein Wert darf leer sein", 28 | "No value can be longer than 100 characters" : "Keiner der eingegebenen Werte darf länger als 100 Zeichen sein", 29 | "Another user with the same email adress already exists" : "Ein Benutzer mit der selben E-Mail Adresse existiert bereits", 30 | "User successfully created" : "Benutzer erfolgreich erstellt", 31 | "Creating user failed" : "Benutzer erstellen fehlgeschlagen", 32 | "Welcome Mail has been send" : "Willkommens E-Mail wurde gesendet", 33 | "Sending the welcome mail failed" : "Senden der Willkommens E-Mail ist fehlgeschlagen", 34 | "Applying forced group memberships failed" : "Verpflichtende Gruppenmitgliedschaften anwenden war nicht erfolgreich", 35 | "Applied forced group memberships" : "Verpflichtende Gruppenmitgliedschaften erfolgreich angewendet", 36 | "Group not found or permission denied" : "Gruppe konnte nicht gefunden werden oder der Zugriff wurde verweigert", 37 | "Welcome to Nextcloud" : "Willkommen bei Nextcloud", 38 | "Welcome to Nextcloud! We hope you enjoy your time here." : "Willkommen bei Nextcloud! Wir hoffen Sie genießen die Zeit hier.", 39 | "Settings saved" : "Einstellungen gespeichert", 40 | "Something went wrong while saving the settings. Please try again." : "Die Einstellungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.", 41 | "Add Group" : "Gruppe erstellen", 42 | "Delete Group" : "Gruppe löschen", 43 | "You are not a member of any group" : "Sie sind noch kein Mitglied einer Gruppe", 44 | "Add Member" : "Mitglied hinzufügen", 45 | "End Group Membership" : "Gruppen Mitgliedschaft beenden", 46 | "Export group member details" : "Details der Gruppenmitglieder exportieren", 47 | "Members" : "Mitglieder", 48 | "Group Admin" : "Gruppen Admin", 49 | "Remove Admin Privileges" : "Admin Privilegien entfernen", 50 | "Make Admin" : "Zum Admin machen", 51 | "Remove" : "Entfernen", 52 | "There are no members in this group yet" : "Diese Gruppe hat noch keine Mitglieder", 53 | "Select a group from the list to view details" : "Wähle ein Gruppe aus der Liste um Details anzuzeigen", 54 | "Group Name" : "Gruppenname", 55 | "Do you really want to remove the group \"{{ group.ldapcontacts_name }}\"?" : "Wollen Sie die Gruppe \"{{ group.ldapcontacts_name }}\" wirklich löschen?", 56 | "Yes" : "Ja", 57 | "No" : "Nein", 58 | "Got it" : "Alles Klar", 59 | "For creating a group click here" : "Klicke hier, um eine Gruppe zu erstellen", 60 | "Be careful! This button will delete a group" : "Achtung! Hiermit löschst du eine Gruppe", 61 | "Search for a user here to add him to this group" : "Suche nach einem Benutzer, um ihn zu der Gruppe hinzuzufügen", 62 | "Here you can leave this group" : "Hier kannst du die Gruppe verlassen", 63 | "All members of this group are listed here" : "Alle Mitglieder dieser Gruppe findest du hier", 64 | "Use this to export all information for every member of this group" : "Damit kannst du alle Informationen über alle Mitglieder dieser Gruppe exportieren", 65 | "LDAP Org Settings" : "LDAP Org Einstellungen", 66 | "Superuser Group" : "Superuser Gruppe", 67 | "Use Password Reset URL" : "Passwort zurücksetzen URL benutzen", 68 | "Password Reset URL" : "Passwort zurücksetzen URL", 69 | "Password Reset Attribute" : "Passwort zurücksetzen Attribut", 70 | "Corresponding LDAP Attribute" : "Zugehöriges LDAP Attribut", 71 | "LDAP Attribute" : "LDAP Attribut", 72 | "Link tag to be replaced" : "Link-Kennzeichnung, die Ersetzt wird", 73 | "Welcome Mail Subject" : "Willkommens Mail Betreff", 74 | "Welcome Mail FROM" : "Willkommens Mail VON", 75 | "E-Mail Address" : "E-Mail Adresse", 76 | "Welcome Mail FROM Name" : "Willkommens Mail VON Name", 77 | "Name" : "Name", 78 | "Welcome Mail Message" : "Willkommens Mail Nachricht", 79 | "Message" : "Nachricht", 80 | "Save" : "Speichern", 81 | "add mandatory group" : "verpflichtende Gruppe hinzufügen", 82 | "Users aren't forced to be a member of any group" : "Die Benutzer sind nicht gezwungen Mitglied einer bestimmten Gruppe zu sein", 83 | "Mandatory group memberships" : "Verpflichtende Gruppenmitgliedschaft", 84 | "Apply forced group memberships to all users" : "Verpflichtende Gruppenmitgliedschaften auf alle Benutzer anwenden", 85 | "LDAP Users" : "LDAP Benutzer", 86 | "Create User" : "Benutzer erstellen", 87 | "Select" : "Auswählen", 88 | "Resend Welcome Mail" : "Willkommens E-Mail erneut senden", 89 | "Delete User" : "Benutzer löschen", 90 | "Create a user" : "Einen Benutzer erstellen", 91 | "Firstname" : "Vorname", 92 | "Lastname" : "Nachname", 93 | "Create" : "Erstellen", 94 | "Abort" : "Abbrechen", 95 | "Do you really want to delete the user {{ user.ldapcontacts_name }}?" : "Wollen Sie den Benutzer \"{{ userldapcontacts_name }}\" wirklich löschen?" 96 | }, 97 | "nplurals=2; plural=(n != 1);"); -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /* navigation area */ 2 | #app-navigation { 3 | position: relative; 4 | } 5 | 6 | #app-navigation > #navigation-header { 7 | border-bottom: 1px solid #eee; 8 | padding: 10px 15px; 9 | z-index: 1; 10 | height: 50px; 11 | } 12 | 13 | #app-navigation a { 14 | display: block; 15 | line-height: 44px; 16 | padding: 0 12px; 17 | } 18 | 19 | #app-navigation > div.info { 20 | height: 100%; 21 | width: 100%; 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | padding: 50px 0 0 0; 26 | } 27 | 28 | #navigation-header > a { 29 | font-size: 16px; 30 | line-height: 29px; 31 | display: block; 32 | position: relative; 33 | z-index: 1; 34 | } 35 | 36 | #navigation-header > a:focus, #navigation-header > a:hover, #navigation-header > a:active { 37 | color: #aaa; 38 | } 39 | 40 | #navigation-header > a > span { 41 | line-height: 23px; 42 | display: inline-block; 43 | vertical-align: top; 44 | } 45 | 46 | #navigation-header > a > i.fa { 47 | margin-right: 7px; 48 | font-size: 25px; 49 | } 50 | 51 | #app-navigation > #navigation-header ~ #group-navigation { 52 | padding-top: 50px; 53 | } 54 | 55 | #app-navigation > #group-navigation { 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | height: 100%; 60 | width: 100%; 61 | } 62 | 63 | #group-navigation > ul { 64 | height: 100%; 65 | overflow: auto; 66 | } 67 | 68 | #group-navigation > ul > li > a:first-child { 69 | padding-right: 50px; 70 | } 71 | 72 | #group-navigation > ul > li > a:not(:first-child) { 73 | cursor: pointer; 74 | position: absolute; 75 | top: 0; 76 | right: 0; 77 | width: auto; 78 | } 79 | 80 | #group-navigation > .icon-loading.centered { 81 | min-height: 50px; 82 | } 83 | /* end of navigation area */ 84 | 85 | 86 | /* content area */ 87 | #info { 88 | padding: 20px; 89 | padding-top: 40px; 90 | } 91 | 92 | #info > h2 { 93 | display: inline-block; 94 | padding-right: 20px; 95 | } 96 | 97 | #info .content-nav { 98 | position: relative; 99 | display: inline-block; 100 | } 101 | 102 | #info .content-nav > .leave { 103 | color: #fe3838; 104 | font-weight: bold; 105 | padding: 5px; 106 | border-radius: 7px; 107 | } 108 | 109 | #info .content-nav > .leave:hover { 110 | color: #FFF; 111 | background-color: #fe3838; 112 | } 113 | 114 | table > tbody > tr > td:first-child { 115 | padding: 5px; 116 | } 117 | 118 | table > tbody > tr > td:last-child { 119 | padding-left: 10px; 120 | padding-right: 5px; 121 | position: relative; 122 | } 123 | 124 | table > tbody > tr > td:last-child > .options { 125 | position: absolute; 126 | background-color: #fff; 127 | color: #333; 128 | border-radius: 3px; 129 | z-index: 110; 130 | left: -17px; 131 | top: 29px; 132 | padding: 5px; 133 | -webkit-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); 134 | -moz-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); 135 | -ms-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); 136 | -o-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); 137 | filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75)); 138 | } 139 | 140 | table > tbody > tr > td:last-child > .options:after { 141 | bottom: 100%; 142 | left: 25px; 143 | border: solid transparent; 144 | content: " "; 145 | height: 0; 146 | width: 0; 147 | position: absolute; 148 | pointer-events: none; 149 | } 150 | 151 | table > tbody > tr > td:last-child > .options:after { 152 | border-bottom-color: #fff; 153 | border-width: 10px; 154 | } 155 | 156 | .members-menu .options a { 157 | display: block; 158 | } 159 | 160 | .members-menu .options a:hover { 161 | color: #a3a3a3; 162 | } 163 | 164 | .members-menu .options a > span.icon { 165 | margin-bottom: 3px; 166 | margin-right: 5px; 167 | } 168 | 169 | .members-menu .options a > i.fa { 170 | margin-left: 2px; 171 | margin-right: 5px; 172 | } 173 | 174 | .members-menu .options a:hover > span.icon-delete { 175 | background-image: url('/core/img/actions/delete-hover.svg?v=1'); 176 | } 177 | /* end of content area */ 178 | 179 | 180 | /* icons */ 181 | .icon { 182 | display: inline-block; 183 | vertical-align: middle; 184 | } 185 | 186 | .icon-loading.centered { 187 | height: 100%; 188 | } 189 | 190 | .icon-more { 191 | display: inline-block; 192 | vertical-align: middle; 193 | background-size: 16px 16px; 194 | opacity: 0.3; 195 | cursor: pointer; 196 | } 197 | 198 | .icon-more:hover { 199 | opacity: 0.7; 200 | } 201 | /* end of icons */ 202 | 203 | 204 | /* messages */ 205 | .msg { 206 | border-radius: 3px; 207 | background-color: #f3c800; 208 | padding: 5px; 209 | display: none; 210 | } 211 | 212 | .msg.success { 213 | color: #fff; 214 | background-color: #47a447; 215 | } 216 | .msg.error { 217 | color: #fff; 218 | background-color: #d2322d; 219 | } 220 | 221 | .msg-container { 222 | height: 25px; 223 | } 224 | /* end of messages */ 225 | 226 | 227 | /* search function */ 228 | .search { 229 | position: relative; 230 | } 231 | 232 | input[type="search"].searching { 233 | padding-right: 25px; 234 | min-width: 100px; 235 | } 236 | 237 | input[type="search"] + .abort { 238 | opacity: 0; 239 | transition: opacity 0.5s; 240 | background: transparent url('../img/remove.svg?v=1') no-repeat center; 241 | background-size: 100%; 242 | width: 0; 243 | height: 20px; 244 | position: absolute; 245 | top: 18px; 246 | right: 12px; 247 | } 248 | 249 | input[type="search"].searching + .abort { 250 | opacity: 0.5; 251 | width: 20px; 252 | cursor: pointer; 253 | margin-right: -4px; 254 | margin-top: -21px; 255 | } 256 | 257 | input[type="search"].searching + .abort:hover, input[type="search"].searching + .abort:focus, input[type="search"].searching + .abort:active { 258 | opacity: 0.8; 259 | } 260 | 261 | .search-suggestions { 262 | display: inline-block; 263 | position: absolute; 264 | top: 32px; 265 | left: 2px; 266 | } 267 | 268 | .search-suggestions > .suggestion { 269 | z-index: 2; 270 | cursor: pointer; 271 | background-color: rgba(240, 240, 240, 1); 272 | padding: 3px 10px; 273 | } 274 | 275 | .search-suggestions > .suggestion:focus, .search-suggestions > .suggestion:hover, .search-suggestions > .suggestion:active { 276 | background-color: rgba(201, 201, 201, 1); 277 | } 278 | 279 | .search-suggestions > .suggestion:first-child { 280 | 281 | } 282 | 283 | .search-suggestions > .suggestion:last-child { 284 | border-radius: 0 0 4px 4px; 285 | } 286 | /* end of search function */ -------------------------------------------------------------------------------- /templates/settings.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright Hornig Software 2017 10 | */ 11 | 12 | /** @var array $_ */ 13 | /** @var \OCP\IL10N $l */ 14 | 15 | script( 'ldaporg', 'settings' ); 16 | style( 'ldaporg', 'settings' ); 17 | ?> 18 |
19 |

t( 'LDAP Org Settings' )); ?>

20 | 84 |
85 | 86 | 87 | 105 | 106 |

t('Mandatory group memberships')); ?>

107 | 108 |
109 |
t( 'Apply forced group memberships to all users' )); ?>
110 |


111 | 112 |

t( 'LDAP Users' )); ?>

113 |
t( 'Create User' )); ?>
114 |
115 | 116 | 124 |
125 | 126 | 152 | 153 | 181 | 190 |
191 |
-------------------------------------------------------------------------------- /js/templates.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var template = Handlebars.template, templates = OCA.LdapOrgTempaltes = OCA.LdapOrgTempaltes || {}; 3 | templates['main_add_group'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 4 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 5 | 6 | return "
\n \n \n
\n
\n"; 13 | },"useData":true}); 14 | templates['main_content'] = template({"1":function(container,depth0,helpers,partials,data) { 15 | var stack1, helper, alias1=container.escapeExpression, alias2=depth0 != null ? depth0 : (container.nullContext || {}); 16 | 17 | return "

" 18 | + alias1(container.lambda(((stack1 = (depth0 != null ? depth0.group : depth0)) != null ? stack1.ldapcontacts_name : stack1), depth0)) 19 | + "

\n\n
\n" 20 | + ((stack1 = helpers["if"].call(alias2,((stack1 = (depth0 != null ? depth0.group : depth0)) != null ? stack1.ldaporg_canedit : stack1),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 21 | + ((stack1 = helpers["if"].call(alias2,(depth0 != null ? depth0.me : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 22 | + "
\n\n" 23 | + ((stack1 = helpers["if"].call(alias2,(depth0 != null ? depth0.exportURL : depth0),{"name":"if","hash":{},"fn":container.program(7, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 24 | + "\n

\n\n\n

" 25 | + alias1(((helper = (helper = helpers.membersTXT || (depth0 != null ? depth0.membersTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias2,{"name":"membersTXT","hash":{},"data":data}) : helper))) 26 | + ((stack1 = helpers["if"].call(alias2,(depth0 != null ? depth0.memberCount : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 27 | + "

\n" 28 | + ((stack1 = helpers["if"].call(alias2,((stack1 = (depth0 != null ? depth0.group : depth0)) != null ? stack1.ldaporg_members : stack1),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.program(24, data, 0),"data":data})) != null ? stack1 : ""); 29 | },"2":function(container,depth0,helpers,partials,data) { 30 | var helper; 31 | 32 | return " \n
\n"; 35 | },"4":function(container,depth0,helpers,partials,data) { 36 | var stack1; 37 | 38 | return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.notForcedMembership : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); 39 | },"5":function(container,depth0,helpers,partials,data) { 40 | var helper; 41 | 42 | return " " 43 | + container.escapeExpression(((helper = (helper = helpers.endGroupMembershipTXT || (depth0 != null ? depth0.endGroupMembershipTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"endGroupMembershipTXT","hash":{},"data":data}) : helper))) 44 | + "\n"; 45 | },"7":function(container,depth0,helpers,partials,data) { 46 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 47 | 48 | return "
" 51 | + alias4(((helper = (helper = helpers.exportGroupDetailsTXT || (depth0 != null ? depth0.exportGroupDetailsTXT : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"exportGroupDetailsTXT","hash":{},"data":data}) : helper))) 52 | + "
\n"; 53 | },"9":function(container,depth0,helpers,partials,data) { 54 | var helper; 55 | 56 | return " (" 57 | + container.escapeExpression(((helper = (helper = helpers.memberCount || (depth0 != null ? depth0.memberCount : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"memberCount","hash":{},"data":data}) : helper))) 58 | + ")"; 59 | },"11":function(container,depth0,helpers,partials,data) { 60 | var stack1; 61 | 62 | return " \n \n" 63 | + ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.group : depth0)) != null ? stack1.ldaporg_canedit : stack1),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.program(20, data, 0),"data":data})) != null ? stack1 : "") 64 | + " \n
\n"; 65 | },"12":function(container,depth0,helpers,partials,data) { 66 | var stack1; 67 | 68 | return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.group : depth0)) != null ? stack1.ldaporg_members : stack1),{"name":"each","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); 69 | },"13":function(container,depth0,helpers,partials,data) { 70 | var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 71 | 72 | return " \n " 73 | + alias4(((helper = (helper = helpers.ldapcontacts_name || (depth0 != null ? depth0.ldapcontacts_name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"ldapcontacts_name","hash":{},"data":data}) : helper))) 74 | + " " 75 | + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.ldaporg_admin : depth0),{"name":"if","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 76 | + " \n \n \n
\n \n
\n \n \n"; 83 | },"14":function(container,depth0,helpers,partials,data) { 84 | var helper; 85 | 86 | return ""; 89 | },"16":function(container,depth0,helpers,partials,data) { 90 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 91 | 92 | return "
  • " 95 | + alias4(((helper = (helper = helpers.removeAdminPrivTXT || (depth0 != null ? depth0.removeAdminPrivTXT : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"removeAdminPrivTXT","hash":{},"data":data}) : helper))) 96 | + "
  • \n "; 97 | },"18":function(container,depth0,helpers,partials,data) { 98 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 99 | 100 | return "
  • " 103 | + alias4(((helper = (helper = helpers.makeAdminTXT || (depth0 != null ? depth0.makeAdminTXT : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"makeAdminTXT","hash":{},"data":data}) : helper))) 104 | + "
  • \n"; 105 | },"20":function(container,depth0,helpers,partials,data) { 106 | var stack1; 107 | 108 | return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),((stack1 = (depth0 != null ? depth0.group : depth0)) != null ? stack1.ldaporg_members : stack1),{"name":"each","hash":{},"fn":container.program(21, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); 109 | },"21":function(container,depth0,helpers,partials,data) { 110 | var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); 111 | 112 | return " \n " 113 | + container.escapeExpression(((helper = (helper = helpers.ldapcontacts_name || (depth0 != null ? depth0.ldapcontacts_name : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"ldapcontacts_name","hash":{},"data":data}) : helper))) 114 | + " " 115 | + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.ldaporg_admin : depth0),{"name":"if","hash":{},"fn":container.program(14, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 116 | + " \n " 117 | + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.ldaporg_admin : depth0),{"name":"if","hash":{},"fn":container.program(22, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 118 | + "\n \n"; 119 | },"22":function(container,depth0,helpers,partials,data) { 120 | return ""; 121 | },"24":function(container,depth0,helpers,partials,data) { 122 | var helper; 123 | 124 | return "

    " 125 | + container.escapeExpression(((helper = (helper = helpers.noMembersTXT || (depth0 != null ? depth0.noMembersTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"noMembersTXT","hash":{},"data":data}) : helper))) 126 | + "

    \n"; 127 | },"26":function(container,depth0,helpers,partials,data) { 128 | var helper; 129 | 130 | return "

    " 131 | + container.escapeExpression(((helper = (helper = helpers.selectGroupTXT || (depth0 != null ? depth0.selectGroupTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"selectGroupTXT","hash":{},"data":data}) : helper))) 132 | + "

    \n"; 133 | },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 134 | var stack1; 135 | 136 | return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.group : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.program(26, data, 0),"data":data})) != null ? stack1 : ""); 137 | },"useData":true}); 138 | templates['main_navigation'] = template({"1":function(container,depth0,helpers,partials,data) { 139 | var stack1; 140 | 141 | return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.groups : depth0),{"name":"each","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); 142 | },"2":function(container,depth0,helpers,partials,data) { 143 | var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 144 | 145 | return "
  • \n " 150 | + alias4(((helper = (helper = helpers.ldapcontacts_name || (depth0 != null ? depth0.ldapcontacts_name : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"ldapcontacts_name","hash":{},"data":data}) : helper))) 151 | + "\n " 152 | + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.superuser : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") 153 | + "\n
  • \n"; 154 | },"3":function(container,depth0,helpers,partials,data) { 155 | return "active"; 156 | },"5":function(container,depth0,helpers,partials,data) { 157 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 158 | 159 | return ""; 164 | },"7":function(container,depth0,helpers,partials,data) { 165 | var helper; 166 | 167 | return "
  • " 168 | + container.escapeExpression(((helper = (helper = helpers.noMemberTXT || (depth0 != null ? depth0.noMemberTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"noMemberTXT","hash":{},"data":data}) : helper))) 169 | + "
  • \n"; 170 | },"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 171 | var stack1; 172 | 173 | return "\n"; 176 | },"useData":true}); 177 | templates['main_navigation_header'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 178 | var helper; 179 | 180 | return "" 181 | + container.escapeExpression(((helper = (helper = helpers.addGroupTXT || (depth0 != null ? depth0.addGroupTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"addGroupTXT","hash":{},"data":data}) : helper))) 182 | + "\n"; 183 | },"useData":true}); 184 | templates['main_remove_group'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 185 | var helper; 186 | 187 | return "
    \n

    " 188 | + container.escapeExpression(((helper = (helper = helpers.questionTXT || (depth0 != null ? depth0.questionTXT : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"questionTXT","hash":{},"data":data}) : helper))) 189 | + "

    \n
    \n\n
    \n \n \n
    \n
    \n"; 190 | },"useData":true}); 191 | templates['main_tutorial'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { 192 | var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; 193 | 194 | return "
    \n
    \n " 195 | + alias4(((helper = (helper = helpers.message || (depth0 != null ? depth0.message : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"message","hash":{},"data":data}) : helper))) 196 | + "\n
    \n
    \n \n
    \n
    \n"; 199 | },"useData":true}); 200 | })(); -------------------------------------------------------------------------------- /js/settings.js: -------------------------------------------------------------------------------- 1 | (function (OC, window, $, undefined) { 2 | 'use strict'; 3 | 4 | $(document).ready(function () { 5 | 6 | Handlebars.registerHelper( 'istrue', function( variable, options ) { 7 | if( variable == 'true' ) { 8 | return options.fn( this ); 9 | } else { 10 | return options.inverse( this ); 11 | } 12 | }); 13 | Handlebars.registerHelper( 'isfalse', function( variable, options ) { 14 | if( variable == 'true' ) { 15 | return options.inverse( this ); 16 | } else { 17 | return options.fn( this ); 18 | } 19 | }); 20 | 21 | Handlebars.registerHelper( 'each_attributes', function( data, attributes, options ) { 22 | var ret = ''; 23 | 24 | // go through every attribute 25 | $.each( attributes, function( key, label ) { 26 | // check if the attribute is present 27 | if( typeof( data[ key ] ) == 'undefined' || data[ key ] == null ) { 28 | return; 29 | } 30 | // add the attribute to the output 31 | ret += options.fn( { label: label, data: data[ key ] } ); 32 | }); 33 | 34 | return ret; 35 | }); 36 | 37 | var Users = function() { 38 | this._baseUrl = OC.generateUrl( '/apps/ldaporg' ); 39 | this._ldapcontacts_baseUrl = OC.generateUrl( '/apps/ldapcontacts' ); 40 | this._users = []; 41 | this._groups = []; 42 | this._settings = []; 43 | this._ldapcontacts_settings = []; 44 | this._lastForcedGroupMembershipsSearch = ''; 45 | this._forcedGroupMembershipsSearchId = 0; 46 | this._checkbox_settings = [ 'superuser_groups' ]; 47 | }; 48 | 49 | Users.prototype = { 50 | init: function() { 51 | var self = this; 52 | $( '#ldaporg-add-user' ).click( function() { 53 | self.renderContent(); 54 | }); 55 | }, 56 | loadLdapContactsSettings: function() { 57 | var deferred = $.Deferred(); 58 | var self = this; 59 | // load the ldapcontact settings 60 | $.get( this._ldapcontacts_baseUrl + '/settings', function( data ) { 61 | if( data.status == 'success' ) { 62 | self._ldapcontacts_settings = data.data; 63 | deferred.resolve(); 64 | } 65 | else { 66 | // settings couldn't be loaded 67 | deferred.reject(); 68 | } 69 | }).fail( function() { 70 | // settings couldn't be loaded 71 | deferred.reject(); 72 | }); 73 | return deferred.promise(); 74 | }, 75 | loadUsers: function() { 76 | var deferred = $.Deferred(); 77 | var self = this; 78 | $.get( this._baseUrl + '/admin/load/users' ).done( function( users ) { 79 | // reset variables 80 | self._users = users; 81 | deferred.resolve(); 82 | }).fail( function() { 83 | deferred.reject(); 84 | }); 85 | return deferred.promise(); 86 | }, 87 | renderUsers: function() { 88 | var self = this; 89 | var source = $( '#ldaporg-existing-users-tpl' ).html(); 90 | var template = Handlebars.compile( source ); 91 | var html = template( { users: self._users } ); 92 | $( '#ldaporg-existing-users' ).html( html ); 93 | 94 | // button for selecting user 95 | $( '#ldaporg-existing-users > .user > span.icon-play' ).click( function() { 96 | var entry_id = $( this ).attr( 'data-id' ); 97 | // show the users details 98 | self.showUserDetails( entry_id ); 99 | }); 100 | }, 101 | showUserDetails: function( entry_id ) { 102 | var user = null; 103 | // look for the user 104 | $.each( this._users, function( key, value ) { 105 | if( value.ldapcontacts_entry_id == entry_id ) { 106 | user = value; 107 | return; 108 | } 109 | }); 110 | 111 | var source = $( '#ldaporg-user-details-tpl' ).html(); 112 | var template = Handlebars.compile( source ); 113 | var html = template( { user: user, attributes: this._ldapcontacts_settings.user_ldap_attributes } ); 114 | $( '#ldaporg-user-content' ).html( html ); 115 | this.registerModifyUserButton(); 116 | }, 117 | registerModifyUserButton: function() { 118 | var self = this; 119 | // button for resending welcome email 120 | $( '#ldaporg-user-content .button.resend-welcome-mail' ).click( function() { 121 | OC.msg.startSaving( '#ldaporg-user-content .msg' ); 122 | var entry_id = $( this ).attr( 'data-id' ); 123 | var user; 124 | // look for the user 125 | $.each( self._users, function( key, value ) { 126 | if( value.ldapcontacts_entry_id == entry_id ) { 127 | user = value; 128 | return; 129 | } 130 | }); 131 | // check if the user was found 132 | if( typeof( user ) == 'undefined' || user == null ) { 133 | OC.msg.finishedError( '#ldaporg-user-content .msg' ); 134 | return; 135 | } 136 | 137 | // resend the welcome email 138 | $.ajax({ 139 | url: self._baseUrl + '/welcomemail', 140 | method: 'POST', 141 | contentType: 'application/json', 142 | data: JSON.stringify( { mail: user[ self._settings.mail_attribute ], name: user.ldapcontacts_name } ) 143 | }).done( function( data ) { 144 | // show message 145 | OC.msg.finishedSaving( '#ldaporg-user-content .msg', data ); 146 | }).fail( function( data ) { 147 | OC.msg.finishedError( '#ldaporg-user-content .msg' ); 148 | }); 149 | }); 150 | 151 | // button for deleting a user 152 | $( '#ldaporg-user-content .button.delete-user' ).click( function() { 153 | var entry_id = $( this ).attr( 'data-id' ); 154 | var user; 155 | // look for the user 156 | $.each( self._users, function( key, value ) { 157 | if( value.ldapcontacts_entry_id == entry_id ) { 158 | user = value; 159 | return; 160 | } 161 | }); 162 | // check if the user was found 163 | if( typeof( user ) == 'undefined' || user == null ) {return; } 164 | 165 | // check if the user should really be deleted 166 | var source = $( '#ldaporg-user-delete-tpl' ).html(); 167 | var template = Handlebars.compile( source ); 168 | var html = template( { user: user } ); 169 | $( '#ldaporg-user-content' ).html( html ); 170 | 171 | // really deleting the user 172 | $( '#ldaporg-delete-user' ).click( function() { 173 | OC.msg.startSaving( '#ldaporg-user-content .msg' ); 174 | // delete the selected user 175 | $.getJSON( self._baseUrl + '/delete/user/' + encodeURI( entry_id ), function( data ) { 176 | // if the deleting was successful, reload all users 177 | if( data.status == 'success' ) { 178 | self.loadUsers().done( function() { 179 | // render the users again 180 | self.renderUsers(); 181 | // render the initial content area 182 | self.renderContent(); 183 | // show a message that the user was deleted 184 | OC.msg.finishedSaving( '#ldaporg-user-content .msg', data ); 185 | }); 186 | } 187 | else { 188 | // show error message 189 | OC.msg.finishedSaving( '#ldaporg-user-content .msg', data ); 190 | } 191 | }).fail( function() { 192 | OC.msg.finishedError( '#ldaporg-user-content .msg' ); 193 | }); 194 | }); 195 | 196 | // aborting the action 197 | $( '#ldaporg-abort-delete-user' ).click( function() { 198 | // render the initial content area 199 | self.renderContent(); 200 | }); 201 | }); 202 | }, 203 | renderContent: function() { 204 | var self = this; 205 | var source = $( '#ldaporg-user-content-tpl' ).html(); 206 | var template = Handlebars.compile( source ); 207 | var html = template(); 208 | $( '#ldaporg-user-content' ).html( html ); 209 | 210 | // creating user button 211 | $( '#ldaporg-create-user' ).click( function( e ) { 212 | e.preventDefault(); 213 | 214 | var data = {}; 215 | data.firstname = $( '#ldaporg-create-user-firstname' ).val(); 216 | data.lastname = $( '#ldaporg-create-user-lastname' ).val(); 217 | data.mail = $( '#ldaporg-create-user-mail' ).val(); 218 | // start saving 219 | OC.msg.startSaving( '#ldaporg-user-content .msg' ); 220 | 221 | // create a user 222 | $.ajax({ 223 | url: self._baseUrl + '/create/user', 224 | method: 'POST', 225 | contentType: 'application/json', 226 | data: JSON.stringify( data ) 227 | }).done( function( data ) { 228 | // if creating the user was successful, reload all users 229 | if( data.status == 'success' ) { 230 | self.loadUsers().done( function() { 231 | // render the users again 232 | self.renderUsers(); 233 | // render the initial content area 234 | self.renderContent(); 235 | }); 236 | } 237 | // show message 238 | OC.msg.finishedSaving( '#ldaporg-user-content .msg', data ); 239 | }).fail( function() { 240 | OC.msg.finishedError( '#ldaporg-user-content .msg' ); 241 | }); 242 | }); 243 | }, 244 | renderSettings: function() { 245 | var deferred = $.Deferred(); 246 | var self = this; 247 | $.get( this._baseUrl + '/settings' ).done( function( settings ) { 248 | self._settings = settings; 249 | self.loadGroups().done( function() { 250 | // mark all superuser groups 251 | $.each( self._groups, function( i, group ) { 252 | if( $.inArray( group.ldapcontacts_entry_id, self._settings.superuser_groups ) != -1 ) { 253 | group.ldaporg_superuser_group = true; 254 | } 255 | }); 256 | 257 | // render the settings area 258 | var source = $( '#ldaporg-edit-settings-tpl' ).html(); 259 | var template = Handlebars.compile( source ); 260 | var html = template( { settings: self._settings, groups: self._groups } ); 261 | $( '#ldaporg-edit-settings' ).html( html ); 262 | 263 | // hide password reset url options when deactivated 264 | $( '#ldaporg_pwd_reset_url_active_false' ).change( function() { 265 | $( '#ldaporg-edit-settings .pwd_reset_url' ).hide(400); 266 | }); 267 | // show password reset url options when activated 268 | $( '#ldaporg_pwd_reset_url_active_true' ).change( function() { 269 | $( '#ldaporg-edit-settings .pwd_reset_url' ).show(400); 270 | }); 271 | 272 | // save the settings 273 | $( '#ldaporg_settings_save' ).click( function( e ) { 274 | e.preventDefault(); 275 | self.saveSettings(); 276 | }); 277 | 278 | deferred.resolve(); 279 | }).fail( function() { 280 | deferred.reject(); 281 | }); 282 | }); 283 | return deferred.promise(); 284 | }, 285 | saveSettings: function() { 286 | var self = this; 287 | var deferred = $.Deferred(); 288 | // get all values from the form 289 | var form = $( '#ldaporg_settings_form' ).serialize(); 290 | // go through all the checkboxes and check if they should be erased completely 291 | $.each( self._checkbox_settings, function( i, setting ) { 292 | // if the setting is not present in the serialized form, 293 | if( !~form.toLowerCase().indexOf( setting ) ) { 294 | form += '&' + setting + '%5B%5D='; 295 | } 296 | }); 297 | 298 | // start saving 299 | OC.msg.startSaving( '#ldaporg_settings_form .msg' ); 300 | 301 | // update the settings 302 | $.ajax({ 303 | url: self._baseUrl + '/settings', 304 | method: 'POST', 305 | contentType: 'application/json', 306 | data: JSON.stringify( { settings: form } ) 307 | }).done( function( data ) { 308 | // reload all settings 309 | self.renderSettings().done( function() { 310 | // saving the settings was successful 311 | OC.msg.finishedSaving( '#ldaporg_settings_form .msg', data ); 312 | }); 313 | }).fail( function( data ) { 314 | OC.msg.finishedError( '#ldaporg_settings_form .msg' ); 315 | }); 316 | return deferred.promise(); 317 | }, 318 | // renders the settings for forcing group memberships 319 | renderForcedGroupMemberships: function () { 320 | var self = this; 321 | var source = $( '#ldaporg-force-group-membership-tpl' ).html(); 322 | var template = Handlebars.compile( source ); 323 | // get the forced groups details 324 | var forced_groups = []; 325 | $.each( this._settings.forced_group_memberships, function( k, forced_group_entry_id ) { 326 | $.each( self._groups, function( k2, group ) { 327 | // check if this is the group 328 | if( group.ldapcontacts_entry_id == forced_group_entry_id ) { 329 | forced_groups.push( group ); 330 | return false; 331 | } 332 | }); 333 | }); 334 | 335 | var html = template( { groups: forced_groups } ); 336 | $( '#ldaporg-force-group-membership' ).html( html ); 337 | 338 | // make a group membership optional again 339 | $('#ldaporg-force-group-membership .remove').click( function() { 340 | // get the groups id 341 | var entry_id = this.attributes['target-id'].value; 342 | // unforce the groups membership 343 | self.unforceGroupMembership( entry_id ); 344 | }); 345 | 346 | // search form for forcing a group membership 347 | $('#ldaporg-search-non-forced-memberships-group').on( "change keyup paste", function() { 348 | var value = $( this ).val(); 349 | 350 | // check if we are still searching 351 | if( value == '' ) $( this ).removeClass( 'searching' ); 352 | else $( this ).addClass( 'searching' ); 353 | 354 | // search for the given value and render the navigation 355 | self.searchNonforcedMembershipGroups( value ); 356 | }); 357 | 358 | // abort the search 359 | $('#ldaporg-search-non-forced-memberships-group + .abort').click( function() { 360 | // clear the search form 361 | $('#ldaporg-search-non-forced-memberships-group').val(''); 362 | $('#ldaporg-search-non-forced-memberships-group').trigger( 'change' ); 363 | }); 364 | 365 | // apply forced group memberships 366 | $( '#ldaporg-apply-forced-group-membership' ).click( function() { 367 | OC.msg.startSaving( '#ldaporg-force-group-membership-msg' ); 368 | // send request 369 | $.get( self._baseUrl + '/apply/forcedMemberships', function( data ) { 370 | OC.msg.finishedSaving( '#ldaporg-force-group-membership-msg', data ); 371 | }).fail( function() { 372 | OC.msg.finishedError( '#ldaporg-force-group-membership-msg' ); 373 | }); 374 | }); 375 | }, 376 | // force the membership of a group 377 | forceGroupMembership: function( group_entry_id ) { 378 | var self = this; 379 | OC.msg.startSaving( '#ldaporg-force-group-membership-msg' ); 380 | 381 | // get the newest forced_group_memberships 382 | self.loadForcedGroupMemberships().done( function() { 383 | // add the new group to the array 384 | self._settings.forced_group_memberships.push( group_entry_id ); 385 | // save the forced groups 386 | self.saveForcedGroupMemberships( self._settings.forced_group_memberships ); 387 | }).fail( function() { 388 | OC.msg.finishedError( '#ldaporg-force-group-membership-msg' ); 389 | }); 390 | }, 391 | // make a certain group membership optional again 392 | unforceGroupMembership: function ( group_entry_id ) { 393 | var self = this; 394 | OC.msg.startSaving( '#ldaporg-force-group-membership-msg' ); 395 | 396 | // get the newest forced_group_memberships 397 | self.loadForcedGroupMemberships().done( function() { 398 | // find the group and delete it 399 | var index = $.inArray( group_entry_id, self._settings.forced_group_memberships ); 400 | delete self._settings.forced_group_memberships[ index ]; 401 | // save the forced groups 402 | self.saveForcedGroupMemberships( self._settings.forced_group_memberships ); 403 | }).fail( function() { 404 | OC.msg.finishedError( '#ldaporg-force-group-membership-msg' ); 405 | }); 406 | }, 407 | saveForcedGroupMemberships: function( forced_groups ) { 408 | var self = this; 409 | // save the new forced_groups 410 | $.ajax({ 411 | url: self._baseUrl + '/setting', 412 | method: 'POST', 413 | contentType: 'application/json', 414 | data: JSON.stringify( { key: 'forced_group_memberships', value: forced_groups } ) 415 | }).done( function( data ) { 416 | // reload all data 417 | self.loadForcedGroupMemberships().done( function() { 418 | self.renderForcedGroupMemberships(); 419 | // show message 420 | if( data ) { 421 | OC.msg.finishedSuccess( '#ldaporg-force-group-membership-msg' ); 422 | } 423 | else { 424 | OC.msg.finishedError( '#ldaporg-force-group-membership-msg' ); 425 | } 426 | }).fail( function() { 427 | OC.msg.finishedError( '#ldaporg-force-group-membership-msg' ); 428 | }); 429 | }).fail( function() { 430 | OC.msg.finishedError( '#ldaporg-force-group-membership-msg' ); 431 | }); 432 | }, 433 | loadForcedGroupMemberships: function() { 434 | var self = this; 435 | var deferred = $.Deferred(); 436 | 437 | // get the newest forced_group_memberships 438 | $.get( this._baseUrl + '/setting/forced_group_memberships', function( forced_groups ) { 439 | self._settings.forced_group_memberships = forced_groups; 440 | deferred.resolve(); 441 | }); 442 | 443 | return deferred.promise(); 444 | }, 445 | searchNonforcedMembershipGroups: function ( search ) { 446 | if( search == this._lastForcedGroupMembershipsSearch ) return false; 447 | this._lastForcedGroupMembershipsSearch = search; 448 | 449 | // if the search form is empty, clean up 450 | if( search == '' ) { 451 | this.renderNonforcedMembershipGroupSuggestions( this._groups ); 452 | return true; 453 | } 454 | 455 | var self = this; 456 | this._forcedGroupMembershipsSearchId++; 457 | var id = this._forcedGroupMembershipsSearchId; 458 | search = search.toLowerCase(); 459 | 460 | var matches = []; 461 | 462 | $( this._groups ).each( function( i, group ) { 463 | if( self._forcedGroupMembershipsSearchId != id ) return false; 464 | $.each( group, function( key, value ) { 465 | if( typeof( value ) != 'string' && typeof( value ) != 'number' ) return; 466 | value = String( value ).toLowerCase(); 467 | if( ~value.indexOf( search ) ) { 468 | matches.push( group ); 469 | return false; 470 | } 471 | }); 472 | }); 473 | return self.renderNonforcedMembershipGroupSuggestions( matches ); 474 | }, 475 | renderNonforcedMembershipGroupSuggestions: function( groups ) { 476 | var self = this; 477 | // clear the suggestions area 478 | $( '#ldaporg-force-group-membership .search + .search-suggestions' ).empty(); 479 | // don't show all groups at once 480 | if( groups != this._groups ) { 481 | // show all found groups 482 | $.each( groups, function( i, group ) { 483 | // render the search suggestion 484 | var html = $( document.createElement( 'div' ) ) 485 | .addClass( 'suggestion' ) 486 | // add the groups name 487 | .text( group.ldapcontacts_name ) 488 | // add the contact information to the suggestion 489 | .data( 'entry_id', group.ldapcontacts_entry_id ) 490 | // when clicked on the group, it will be hidden 491 | .click( function() { 492 | self.forceGroupMembership( $( this ).data( 'entry_id' ) ); 493 | }); 494 | // add the option to the search suggestions 495 | $( '#ldaporg-force-group-membership .search + .search-suggestions' ).append( html ); 496 | }); 497 | } 498 | 499 | return true; 500 | }, 501 | // load all visible and unvisible groups 502 | loadGroups: function() { 503 | var deferred = $.Deferred(); 504 | var self = this; 505 | // load the groups 506 | $.get( this._baseUrl + '/admin/load/groups', function( data ) { 507 | self._groups = data; 508 | deferred.resolve(); 509 | }).fail( function() { 510 | // groups couldn't be loaded 511 | deferred.reject(); 512 | }); 513 | return deferred.promise(); 514 | } 515 | }; 516 | 517 | var users = new Users(); 518 | users.init(); 519 | 520 | users.loadUsers().done( function(){ 521 | users.renderContent(); 522 | users.renderUsers(); 523 | }); 524 | users.loadLdapContactsSettings().done( function() { 525 | users.renderSettings().done( function() { 526 | users.loadGroups().done( function() { 527 | users.renderForcedGroupMemberships(); 528 | }); 529 | }); 530 | }); 531 | }); 532 | })(OC, window, jQuery); -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | (function (OC, window, $, undefined) { 2 | 'use strict'; 3 | 4 | $(document).ready(function() { 5 | 6 | // main rendering object 7 | var Groups = function() { 8 | this._baseUrl = OC.generateUrl( '/apps/ldaporg' ); 9 | this._ldapContactsbaseUrl = OC.generateUrl( '/apps/ldapcontacts' ); 10 | this._activeGroup = undefined; 11 | this._groups = []; 12 | this._users = []; 13 | this._settings = []; 14 | this._me = undefined; 15 | this._last_search = ''; 16 | this._search_id = 0; 17 | this._superuser = false; 18 | }; 19 | 20 | // all group related functions 21 | Groups.prototype = { 22 | // load all groups you have access to 23 | loadGroups: function() { 24 | var deferred = $.Deferred(); 25 | var self = this; 26 | $.get( this._baseUrl + '/load/groups' ).done( function( data ) { 27 | if( data.status == 'success' ) { 28 | // save the fetched groups 29 | self._groups = data.data; 30 | // reload the active group if there was one 31 | if( typeof( self._activeGroup ) != 'undefined' && self._activeGroup != null ) { 32 | self.load( self._activeGroup.ldapcontacts_entry_id, true ); 33 | } 34 | deferred.resolve(); 35 | } 36 | else { 37 | deferred.reject(); 38 | } 39 | }).fail( function () { 40 | deferred.reject(); 41 | }); 42 | return deferred.promise(); 43 | }, 44 | // load all existing users 45 | loadUsers: function() { 46 | var deferred = $.Deferred(); 47 | var self = this; 48 | $.get( this._baseUrl + '/load/users' ).done( function( data ) { 49 | if( data.status == 'success' ) { 50 | // save the fetched users 51 | self._users = data.data; 52 | deferred.resolve(); 53 | } 54 | else { 55 | deferred.reject(); 56 | } 57 | }).fail(function () { 58 | deferred.reject(); 59 | }); 60 | return deferred.promise(); 61 | }, 62 | // load the own user 63 | loadOwn: function() { 64 | var deferred = $.Deferred(); 65 | var self = this; 66 | $.get( this._ldapContactsbaseUrl + '/own' ).done( function( me ) { 67 | // save the fetched users 68 | self._me = me[0]; 69 | deferred.resolve(); 70 | }).fail(function () { 71 | deferred.reject(); 72 | }); 73 | return deferred.promise(); 74 | }, 75 | // load all required data 76 | loadAll: function() { 77 | var deferred = $.Deferred(); 78 | var self = this; 79 | // load groups 80 | this.loadGroups().done( function() { 81 | // load users 82 | self.loadUsers().done( function() { 83 | // load current user 84 | self.loadOwn().done( function() { 85 | // load forced group memberships 86 | self.loadSettings().done( function() { 87 | deferred.resolve(); 88 | }).fail( function() { 89 | deferred.reject(); 90 | }); 91 | }).fail( function() { 92 | deferred.reject(); 93 | }); 94 | }).fail( function() { 95 | deferred.reject(); 96 | }); 97 | }).fail( function() { 98 | deferred.reject(); 99 | }); 100 | return deferred.promise(); 101 | }, 102 | loadSettings: function() { 103 | var deferred = $.Deferred(); 104 | var self = this; 105 | $.get( this._baseUrl + '/settings' ).done( function( settings ) { 106 | self._settings = settings; 107 | deferred.resolve(); 108 | }).fail( function( data ) { 109 | deferred.reject(); 110 | }); 111 | return deferred.promise(); 112 | }, 113 | // load a groups data and render it in the content area if needed 114 | load: function( entry_id, no_render ) { 115 | var self = this; 116 | this._activeGroup = this._groups[ entry_id ]; 117 | 118 | // render the content if needed 119 | if( typeof( no_render ) == 'undefined' || !no_render ) 120 | return this.renderContent( true ); 121 | }, 122 | // render everything 123 | render: function() { 124 | var deferred = $.Deferred(); 125 | var self = this; 126 | this.renderContent().done( function() { 127 | self.renderNavigationHeader().done( function() { 128 | self.renderNavigation(); 129 | deferred.resolve(); 130 | }).fail(function () { 131 | deferred.reject(); 132 | }); 133 | }).fail(function () { 134 | deferred.reject(); 135 | }); 136 | return deferred.promise(); 137 | }, 138 | // render navigation header 139 | renderNavigationHeader: function() { 140 | var deferred = $.Deferred(); 141 | var self = this; 142 | 143 | // check if the current user has superuser rights 144 | $.get( this._baseUrl + '/superuser' ).done( function( data ) { 145 | var superuser = ( data.status == 'success' && data.data ) ? true : false; 146 | // if the user is not a superuser, don't show the special options 147 | if( !superuser ) { 148 | $( '#navigation-header' ).remove(); 149 | // remove the superuser attribute from every group 150 | $.each( self._groups, function( key, value ) { 151 | self._groups[ key ].superuser = false; 152 | }); 153 | self._superuser = false; 154 | 155 | deferred.resolve(); 156 | return; 157 | } 158 | 159 | // add the superuser attribute to every group 160 | $.each( self._groups, function( key, value ) { 161 | self._groups[ key ].superuser = true; 162 | }); 163 | self._superuser = true; 164 | 165 | // show special superuser options 166 | var html = OCA.LdapOrgTempaltes.main_navigation_header({ 167 | addGroupTXT: t('ldaporg', 'Add Group') 168 | }); 169 | $( '#navigation-header' ).html( html ); 170 | 171 | // add action for creating a group 172 | $( '#add-group' ).click( function() { 173 | // no group is active at the moment 174 | self._activeGroup = undefined; 175 | // load the adding form into the content area 176 | var html = OCA.LdapOrgTempaltes.main_add_group({ 177 | nameTXT: t('ldaporg', 'Group Name'), 178 | addTXT: t('ldaporg', 'Add Group') 179 | }); 180 | $( '#info' ).html( html ); 181 | 182 | $( '#add-group-create' ).click( function( e ) { 183 | e.preventDefault(); 184 | var deferred = $.Deferred(); 185 | OC.msg.startSaving( '#info .msg' ); 186 | 187 | // create a new group 188 | $.ajax({ 189 | url: self._baseUrl + '/create/group', 190 | method: 'POST', 191 | contentType: 'application/json', 192 | data: JSON.stringify( { group_name: $( '#add-group-name' ).val() } ) 193 | }).done( function( data ) { 194 | // if the creating was successful, reload all groups 195 | if( data.status == 'success' ) { 196 | self.loadAll().done( function() { 197 | self.render(); 198 | // load the newly created group into the content area 199 | self.load( data.gid ).done( function() { 200 | // show group created message 201 | OC.msg.finishedSaving( '#info .msg', data ); 202 | deferred.resolve(); 203 | }); 204 | }); 205 | } 206 | else { 207 | // show error message 208 | OC.msg.finishedSaving( '#info .msg', data ); 209 | deferred.resolve(); 210 | } 211 | }); 212 | return deferred.promise(); 213 | }); 214 | }); 215 | deferred.resolve(); 216 | }); 217 | return deferred.promise(); 218 | }, 219 | // render group overview 220 | renderNavigation: function() { 221 | var self = this; 222 | 223 | var html = OCA.LdapOrgTempaltes.main_navigation({ 224 | groups: this._groups, 225 | deleteTXT: t('ldaporg', 'Delete Group'), 226 | noMemberTXT: t('ldaporg', 'You are not a member of any group') 227 | }); 228 | $( '#group-navigation' ).html( html ); 229 | 230 | // load a group 231 | $( '#group-navigation .group > a.load' ).click( function() { 232 | var entry_id = $( this ).parent().data( 'id' ); 233 | self.load( entry_id ); 234 | }); 235 | 236 | // remove a group if the user is a superuser 237 | if( this._superuser ) { 238 | $( '#group-navigation .group > a > span.icon-delete' ).click( function( e ) { 239 | var group_entry_id = $( this ).attr( 'data-id' ); 240 | // ask the user if he really wants to delete this group 241 | 242 | // get the group 243 | var group = self._groups[ group_entry_id ]; 244 | // check if the group was found 245 | if( typeof( group ) == 'undefined' || group == null ) return; 246 | 247 | var html = OCA.LdapOrgTempaltes.main_remove_group({ 248 | yesTXT: t('ldaporg', 'Yes'), 249 | noTXT: t('ldaporg', 'No'), 250 | questionTXT: t('ldaporg', 'Do you really want to remove the group {groupName]', { 251 | groupName: group.ldapcontacts_name 252 | }) 253 | }); 254 | $( '#info' ).html( html ); 255 | 256 | // really remove button 257 | $( '#remove-group' ).click( function() { 258 | var deferred = $.Deferred(); 259 | OC.msg.startSaving( '#info .msg' ); 260 | 261 | // remove the group 262 | $.get( self._baseUrl + '/delete/group/' + encodeURI( group_entry_id ) ).done( function( data ) { 263 | // if the removing was successful, reload all groups 264 | if( data.status == 'success' ) { 265 | self.loadAll().done( function() { 266 | // clear selected group 267 | self._activeGroup = undefined; 268 | // render the initial content 269 | self.render().done( function() { 270 | // show group deleted message 271 | OC.msg.finishedSaving( '#info .msg', data ); 272 | deferred.resolve(); 273 | }); 274 | }); 275 | } 276 | else { 277 | // show error message 278 | OC.msg.finishedSaving( '#info .msg', data ); 279 | deferred.reject(); 280 | } 281 | }).fail( function() { 282 | // show error message 283 | OC.msg.finishedError( '#info .msg' ); 284 | deferred.reject(); 285 | }); 286 | return deferred.promise(); 287 | }); 288 | // abort button 289 | $( '#abort-remove-group' ).click( function() { 290 | // load the initial content 291 | self.load(); 292 | }); 293 | }); 294 | } 295 | }, 296 | // render the content area 297 | renderContent: function( load ) { 298 | // show loading icon if needed 299 | if( typeof( load ) != 'undefined' && load != null ) { 300 | $( '#info' ).html( Handlebars.compile( $( '#loading-tpl' ).html() )() ); 301 | } 302 | 303 | var self = this; 304 | 305 | var group_entry_id; 306 | if( typeof( self._activeGroup ) != 'undefined' && self._activeGroup != null ) { 307 | group_entry_id = self._activeGroup.ldapcontacts_entry_id; 308 | } 309 | 310 | // check if the user is allowed to edit this group 311 | return $.get( this._baseUrl + '/canedit/' + encodeURI( group_entry_id ) ).done( function( data ) { 312 | var canedit = data.status == 'success' && data.data ? true : false; 313 | 314 | var html_option = { group: self._activeGroup, notForcedMembership: true }; 315 | 316 | // check if a group has been selected 317 | if( typeof( self._activeGroup ) != 'undefined' && self._activeGroup != null ) { 318 | // check the users editing rights on the group 319 | if( canedit === true ) self._activeGroup.ldaporg_canedit = true; 320 | else delete self._activeGroup.ldaporg_canedit; 321 | 322 | // check if the current user is in the group 323 | var is_member = self._activeGroup.ldaporg_members[ self._me.ldapcontacts_entry_id ]; 324 | if( typeof( is_member ) != 'undefined' && is_member != null ) { 325 | html_option.me = true; 326 | } 327 | 328 | // check if the current group has forced membership 329 | $.each( self._settings.forced_group_memberships, function( key, entry_id ) { 330 | // check if this is the active group 331 | if( self._activeGroup.ldapcontacts_entry_id == entry_id ) { 332 | html_option.notForcedMembership = false; 333 | return false; 334 | } 335 | }); 336 | 337 | // get number of members 338 | html_option.memberCount = self._activeGroup.ldaporg_members.length; 339 | 340 | // add export member details url 341 | html_option.exportURL = self._baseUrl + '/export/' + encodeURI( self._activeGroup.ldapcontacts_entry_id ); 342 | } 343 | else { 344 | canedit = false; 345 | } 346 | 347 | // render content 348 | $( '#info' ).html( OCA.LdapOrgTempaltes.main_content( _.extend({ 349 | addMemberTXT: t('ldaporg', 'Add Member'), 350 | endGroupMembershipTXT: t('ldaporg', 'End Group Membership'), 351 | exportGroupDetailsTXT: t('ldaporg', 'Export group member details'), 352 | membersTXT: t('ldaporg', 'Members'), 353 | groupAdminTXT: t('ldaporg', 'Group Admin'), 354 | removeAdminPrivTXT: t('ldaporg', 'Remove Admin Privileges'), 355 | makeAdminTXT: t('ldaporg', 'Make Admin'), 356 | removeTXT: t('ldaporg', 'Remove'), 357 | noMembersTXT: t('ldaporg', 'There are no members in this group yet'), 358 | selectGroupTXT: t('ldaporg', 'Select a group from the list to view details') 359 | }, html_option) ) ); 360 | $( '#info' ).focus(); 361 | 362 | // button for leaving the group 363 | $( "#leave_group" ).click( function() { 364 | // remove the current user 365 | self.removeUser( self._me.ldapcontacts_entry_id ); 366 | }); 367 | 368 | // admin only functions 369 | if( canedit ) { 370 | $( '#group_add_member' ).on( "change keyup paste", function() { 371 | var value = $( this ).val(); 372 | 373 | // check if we are still searching 374 | if( value == '' ) $( this ).removeClass( 'searching' ); 375 | else $( this ).addClass( 'searching' ); 376 | 377 | // search for the given value and render the navigation 378 | self.searchUsers( value ); 379 | }); 380 | 381 | // button for clearing search input 382 | $( "#group_add_member + .abort" ).click( function() { 383 | $( "#group_add_member" ).val(''); 384 | $( "#group_add_member" ).trigger( 'change' ); 385 | }); 386 | 387 | // expanding menu 388 | $( ".members-menu > td > a" ).click( function( e ) { 389 | e.stopPropagation(); 390 | var target = e.target; 391 | var visible = $( ".options", target.parentElement ).is( ":visible" ); 392 | 393 | // hide all options 394 | $( ".options", target.parentElement.parentElement.parentElement ).hide(); 395 | // now open the options for this element 396 | if( visible ) 397 | $( ".options", target.parentElement ).hide(); 398 | else 399 | $( ".options", target.parentElement ).show(); 400 | 401 | // hide the options again if the user clicks somewhere else 402 | $( document ).click( function() { 403 | $( ".options", target.parentElement ).hide(); 404 | }); 405 | }); 406 | 407 | // admin options for each user 408 | $( ".members-menu > td > .options a" ).click( function( e ) { 409 | var target = $( this ); 410 | // get the user id and required action 411 | var action = target.attr( "data-action" ); 412 | var user_entry_id = target.attr( "data-id" ); 413 | 414 | // choose the right action 415 | switch( action ) { 416 | case "addAdmin": 417 | self.addAdminUser( user_entry_id ); 418 | break; 419 | case "removeAdmin": 420 | self.removeAdminUser( user_entry_id ); 421 | break; 422 | case "remove": 423 | self.removeUser( user_entry_id ); 424 | break; 425 | } 426 | }); 427 | } 428 | }); 429 | }, 430 | searchUsers: function ( search ) { 431 | if( search == this._last_search ) return false; 432 | this._last_search = search; 433 | 434 | // if the search form is empty, clean up 435 | if( search == '' ) { 436 | this.renderUserSuggestions( this._users ); 437 | return true; 438 | } 439 | 440 | var self = this; 441 | this._search_id++; 442 | var id = this._search_id; 443 | search = search.toLowerCase(); 444 | 445 | var matches = []; 446 | 447 | $( this._users ).each( function( i, user ) { 448 | if( self._search_id != id ) return false; 449 | $.each( user, function( key, value ) { 450 | if( typeof( value ) != 'string' && typeof( value ) != 'number' ) return; 451 | value = String( value ).toLowerCase(); 452 | 453 | if( ~value.indexOf( search ) ) { 454 | var in_group = self._activeGroup.ldaporg_members[ user.ldapcontacts_entry_id ]; 455 | in_group = ( typeof( in_group ) == 'undefined' || in_group == null ) ? false : true; 456 | 457 | // check if the user was identified as a member of the group 458 | if( !in_group && $.inArray( user, matches ) == -1 ) { 459 | matches.push( user ); 460 | } 461 | return; 462 | } 463 | }); 464 | }); 465 | 466 | return self.renderUserSuggestions( matches ) 467 | }, 468 | renderUserSuggestions: function( users ) { 469 | var self = this; 470 | // clear the suggestions area 471 | $( '#info .content-nav > .search + .search-suggestions' ).empty(); 472 | // don't show all users at once 473 | if( users != this._users ) { 474 | // show all found users 475 | $.each( users, function( i, user ) { 476 | // render the search suggestion 477 | var html = $( document.createElement( 'div' ) ) 478 | .addClass( 'suggestion' ) 479 | // add the users name 480 | .text( user.ldapcontacts_name ) 481 | // add the user information to the suggestion 482 | .data( 'user', user ) 483 | // when clicked on the user, he will be added to the group 484 | .click( function() { 485 | var user = $( this ).data( 'user' ); 486 | // clear the search box and suggestions 487 | $( "#group_add_member" ).val(''); 488 | $( "#group_add_member" ).trigger( 'change' ); 489 | // add the user to the group 490 | self.addUserToGroup( user.ldapcontacts_entry_id ); 491 | }); 492 | 493 | // add the option to the search suggestions 494 | $( '#info .content-nav > .search + .search-suggestions' ).append( html ); 495 | }); 496 | } 497 | 498 | return true; 499 | }, 500 | // add a user the currently active group 501 | addUserToGroup: function( user_entry_id ) { 502 | var self = this; 503 | OC.msg.startSaving( '#info .msg' ); 504 | 505 | // add the user to the group 506 | return $.ajax({ 507 | url: this._baseUrl + '/add/group/user', 508 | method: 'POST', 509 | contentType: 'application/json', 510 | data: JSON.stringify( { user_entry_id: user_entry_id, group_entry_id: self._activeGroup.ldapcontacts_entry_id } ) 511 | }).done( function( data ) { 512 | self.loadAll().done( function() { 513 | self.render().done( function() { 514 | OC.msg.finishedSaving( '#info .msg', data ); 515 | }).fail( function() { 516 | OC.msg.finishedError( '#info .msg' ); 517 | }); 518 | }).fail( function() { 519 | OC.msg.finishedError( '#info .msg' ); 520 | }); 521 | }).fail( function() { 522 | OC.msg.finishedError( '#info .msg' ); 523 | }); 524 | }, 525 | // remove a user from the currently active group 526 | removeUser: function( user_entry_id ) { 527 | var self = this; 528 | OC.msg.startSaving( '#info .msg' ); 529 | 530 | // add the user to the group 531 | return $.ajax({ 532 | url: this._baseUrl + '/remove/group/user', 533 | method: 'POST', 534 | contentType: 'application/json', 535 | data: JSON.stringify( { user_entry_id: user_entry_id, group_entry_id: this._activeGroup.ldapcontacts_entry_id } ) 536 | }).done( function( data ) { 537 | self.loadAll().done( function() { 538 | self.render().done( function() { 539 | OC.msg.finishedSaving( '#info .msg', data ); 540 | }).fail( function() { 541 | OC.msg.finishedError( '#info .msg' ); 542 | }); 543 | }).fail( function() { 544 | OC.msg.finishedError( '#info .msg' ); 545 | }); 546 | }).fail( function() { 547 | OC.msg.finishedError( '#info .msg' ); 548 | }); 549 | }, 550 | // add admin privileges to a user the currently active group 551 | addAdminUser: function( user_entry_id ) { 552 | var self = this; 553 | OC.msg.startSaving( '#info .msg' ); 554 | 555 | // add the user to the group 556 | return $.ajax({ 557 | url: this._baseUrl + '/add/group/user/admin', 558 | method: 'POST', 559 | contentType: 'application/json', 560 | data: JSON.stringify( { user_entry_id: user_entry_id, group_entry_id: this._activeGroup.ldapcontacts_entry_id } ) 561 | }).done( function( data ) { 562 | self.loadAll().done( function() { 563 | self.render().done( function() { 564 | OC.msg.finishedSaving( '#info .msg', data ); 565 | }).fail( function() { 566 | OC.msg.finishedError( '#info .msg' ); 567 | }); 568 | }).fail( function() { 569 | OC.msg.finishedError( '#info .msg' ); 570 | }); 571 | }).fail( function() { 572 | OC.msg.finishedError( '#info .msg' ); 573 | }); 574 | }, 575 | // remove admin privileges from a user from the currently active group 576 | removeAdminUser: function( user_entry_id ) { 577 | var self = this; 578 | OC.msg.startSaving( '#info .msg' ); 579 | 580 | // add the user to the group 581 | return $.ajax({ 582 | url: this._baseUrl + '/remove/group/user/admin', 583 | method: 'POST', 584 | contentType: 'application/json', 585 | data: JSON.stringify( { user_entry_id: user_entry_id, group_entry_id: this._activeGroup.ldapcontacts_entry_id } ) 586 | }).done( function( data ) { 587 | self.loadAll().done( function() { 588 | self.render().done( function() { 589 | OC.msg.finishedSaving( '#info .msg', data ); 590 | }).fail( function() { 591 | OC.msg.finishedError( '#info .msg' ); 592 | }); 593 | }).fail( function() { 594 | OC.msg.finishedError( '#info .msg' ); 595 | }); 596 | }).fail( function() { 597 | OC.msg.finishedError( '#info .msg' ); 598 | }); 599 | } 600 | }; 601 | 602 | 603 | 604 | 605 | var Tutorial = function () { 606 | this._baseUrl = OC.generateUrl( '/apps/ldaporg' ); 607 | this._state = 0; 608 | this._max_state = 6; 609 | this._parents = [ 610 | "#group-navigation > ul", 611 | "#group-navigation > ul", 612 | "#group-navigation > ul", 613 | "#info > .content-nav", 614 | "#info > .content-nav", 615 | "#info > h3", 616 | "#export_member_details", 617 | ]; 618 | }; 619 | 620 | Tutorial.prototype = { 621 | // get the users current state 622 | getState: function() { 623 | var deferred = $.Deferred(); 624 | var self = this; 625 | 626 | // send request for the users setting 627 | $.get( this._baseUrl + '/settings/personal/tutorial_state' ).done( function( state ) { 628 | // check if the value is valid 629 | if( Math.floor( state ) != state || !$.isNumeric( state ) ) state = 0; 630 | // set the users state 631 | self._state = state; 632 | return deferred.resolve(); 633 | }).fail( function(data) { 634 | deferred.reject(); 635 | }); 636 | return deferred.promise(); 637 | }, 638 | // gets the message for the current tutorial 639 | getMessage: function() { 640 | return $( $( '#tutorial-translations' ).children( 'p' )[ this._state ] ).text(); 641 | }, 642 | // gets the parent element for the current tutorial to be placed in 643 | getTutorialParent: function() { 644 | return $( this._parents[ this._state ] ); 645 | }, 646 | // execute a custom 647 | doCustomAction: function() { 648 | if( this._state == 0 || this._state == "0" ) 649 | { 650 | if( $( document ).width() < 769 && groups._superuser ) 651 | $( '#app-content' ).css( 'transform', 'translate3d(250px, 0px, 0px)' ); 652 | return !groups._superuser; 653 | } 654 | else if( this._state == 1 || this._state == "1" ) 655 | { 656 | if( $( document ).width() < 769 ) 657 | $( '#app-content' ).css( 'transform', 'translate3d(250px, 0px, 0px)' ); 658 | } 659 | else if( this._state == 2 || this._state == "2" ) 660 | { 661 | if( $( document ).width() < 769 && groups._superuser ) 662 | $( '#app-content' ).css( 'transform', 'translate3d(250px, 0px, 0px)' ); 663 | return !groups._superuser; 664 | } 665 | else if( this._state == 3 || this._state == "3" ) 666 | { 667 | if( $( document ).width() < 769 ) 668 | $( '#app-content' ).css( 'transform', 'translate3d(0px, 0px, 0px)' ); 669 | if( typeof( groups._activeGroup ) == 'undefined' || groups._activeGroup == null ) 670 | return groups.load( $( '#group-navigation > ul > li:first-child' ).attr( 'data-id' ) ); 671 | } 672 | else if( this._state == 4 || this._state == "4" ) 673 | { 674 | if( $( document ).width() < 769 ) 675 | $( '#app-content' ).css( 'transform', 'translate3d(0px, 0px, 0px)' ); 676 | if( typeof( groups._activeGroup ) == 'undefined' || groups._activeGroup == null ) 677 | return groups.load( $( '#group-navigation > ul > li:first-child' ).attr( 'data-id' ) ); 678 | } 679 | else if( this._state == 5 || this._state == "5" ) 680 | { 681 | if( $( document ).width() < 769 ) 682 | $( '#app-content' ).css( 'transform', 'translate3d(0px, 0px, 0px)' ); 683 | if( typeof( groups._activeGroup ) == 'undefined' || groups._activeGroup == null ) 684 | return groups.load( $( '#group-navigation > ul > li:first-child' ).attr( 'data-id' ) ); 685 | } 686 | else if( this._state == 6 || this._state == "6" ) 687 | { 688 | if( $( document ).width() < 769 ) 689 | $( '#app-content' ).css( 'transform', 'translate3d(0px, 0px, 0px)' ); 690 | if( typeof( groups._activeGroup ) == 'undefined' || groups._activeGroup == null ) 691 | return groups.load( $( '#group-navigation > ul > li:first-child' ).attr( 'data-id' ) ); 692 | } 693 | }, 694 | // show the next tutorial step and hide the current one 695 | next: function() { 696 | var self = this; 697 | // remove the current tutorial 698 | $( '#tutorial-container' ).remove(); 699 | 700 | // save the current tutorial state 701 | this.saveState(); 702 | // check if the user is already up to date with this tutorials 703 | if( this._state > this._max_state ) return; 704 | 705 | // do custom action 706 | var action_result = this.doCustomAction(); 707 | 708 | // check if an ajax request is running 709 | if( typeof( action_result ) != 'undefined' && typeof( action_result.readyState ) != 'undefined' ) { 710 | action_result.done( function() { 711 | // render new tutorial 712 | var html = OCA.LdapOrgTempaltes.main_tutorial({ 713 | message: self.getMessage(), 714 | gotItTXT: t('ldaporg', 'Got it') 715 | }); 716 | self.getTutorialParent().append( html ); 717 | // add custom attribute 718 | $( '#tutorial-container' ).attr( "tutorial-id", self._state ).slideDown( 300 ); 719 | 720 | // increase state count 721 | self._state++; 722 | 723 | // add action for going to the next tutorial 724 | $( '#tutorial-next' ).one( 'click', function() { 725 | self.next(); 726 | }); 727 | }); 728 | } 729 | // check if the custom action wants to go to the next tutorial 730 | else if( action_result ) { 731 | // increase state count 732 | this._state++; 733 | this.next(); 734 | } 735 | // keep going normally 736 | else { 737 | 738 | // render new tutorial 739 | var html = OCA.LdapOrgTempaltes.main_tutorial({ 740 | message: self.getMessage(), 741 | gotItTXT: t('ldaporg', 'Got it') 742 | }); 743 | this.getTutorialParent().append( html ); 744 | // add custom attribute 745 | $( '#tutorial-container' ).attr( "tutorial-id", this._state ).slideDown( 300 ); 746 | 747 | // increase state count 748 | this._state++; 749 | // add action for going to the next tutorial 750 | $( '#tutorial-next' ).one( 'click', function() { 751 | self.next(); 752 | }); 753 | } 754 | }, 755 | // when the user finished the current tutorial, save his tutorial status 756 | saveState: function() { 757 | var settings = new Object(); 758 | settings.key = 'tutorial_state'; 759 | settings.value = this._state; 760 | 761 | // save the state 762 | return $.ajax({ 763 | url: this._baseUrl + '/settings/personal', 764 | method: 'POST', 765 | contentType: 'application/json', 766 | data: JSON.stringify( settings ) 767 | }); 768 | }, 769 | }; 770 | 771 | var tutorial = new Tutorial(); 772 | 773 | var groups = new Groups(); 774 | groups.loadAll().done( function() { 775 | groups.render().done( function() { 776 | // only show the tutorial if the user is in at least one group 777 | if( groups._groups.length < 1 ) return; 778 | 779 | // load the tutorial 780 | tutorial.getState().done( function() { 781 | // show the first tutorial text 782 | tutorial.next(); 783 | }); 784 | }); 785 | }); 786 | }); 787 | 788 | })(OC, window, jQuery); 789 | -------------------------------------------------------------------------------- /css/fa-4.7.0/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------