Initial commit

This commit is contained in:
philipp lang 2023-03-22 00:23:34 +01:00
commit 444c69672f
15 changed files with 637 additions and 0 deletions

104
Plugin.php Normal file
View File

@ -0,0 +1,104 @@
<?php
namespace Zoomyboy\Owncloud;
use Backend;
use Backend\Models\UserRole;
use System\Classes\PluginBase;
use Zoomyboy\Owncloud\Components\Confirm;
use Zoomyboy\Owncloud\Components\Form;
use Zoomyboy\Owncloud\Models\Settings;
/**
* owncloud Plugin Information File.
*/
class Plugin extends PluginBase
{
/**
* Returns information about this plugin.
*/
public function pluginDetails(): array
{
return [
'name' => 'zoomyboy.owncloud::lang.plugin.name',
'description' => 'zoomyboy.owncloud::lang.plugin.description',
'author' => 'zoomyboy',
'icon' => 'icon-leaf',
];
}
/**
* Register method, called when the plugin is first registered.
*/
public function register(): void
{
}
/**
* Boot method, called right before the request route.
*/
public function boot(): void
{
}
/**
* Registers any frontend components implemented in this plugin.
*/
public function registerComponents(): array
{
return [
Form::class => 'owncloud_form',
Confirm::class => 'owncloud_confirm',
];
}
/**
* Registers any backend permissions used by this plugin.
*/
public function registerPermissions(): array
{
return []; // Remove this line to activate
return [
'zoomyboy.owncloud.some_permission' => [
'tab' => 'zoomyboy.owncloud::lang.plugin.name',
'label' => 'zoomyboy.owncloud::lang.permissions.some_permission',
'roles' => [UserRole::CODE_DEVELOPER, UserRole::CODE_PUBLISHER],
],
];
}
/**
* Registers backend navigation items for this plugin.
*/
public function registerNavigation(): array
{
return []; // Remove this line to activate
return [
'owncloud' => [
'label' => 'zoomyboy.owncloud::lang.plugin.name',
'url' => Backend::url('zoomyboy/owncloud/mycontroller'),
'icon' => 'icon-leaf',
'permissions' => ['zoomyboy.owncloud.*'],
'order' => 500,
],
];
}
public function registerSettings(): array
{
return [
'settings' => [
'label' => 'Owncloud Settings',
'description' => 'Owncloud Zugangsdaten',
'category' => 'Services',
'icon' => 'icon-cog',
'class' => Settings::class,
'order' => 500,
'keywords' => 'owncloud api',
'permissions' => [],
],
];
}
}

View File

@ -0,0 +1,67 @@
var toastedOptions = {
position: "bottom-right",
duration: 3000,
fitToScreen: false,
fullWidth: false,
theme: "material",
};
export default function (toasted) {
var toasted = new toasted(toastedOptions);
return {
loading: false,
data: {
firstname: "",
lastname: "",
username: "",
email: "",
group: null,
function: '',
oc_groups: [],
},
oc_groups: [],
finished: false,
submitRequest: null,
errorFields: [],
groups: [
{ id: "Gallier", name: "Gallier (Wuppertal)" },
{ id: "Gandalf", name: "Gandalf (SG-Mangenberg)" },
{ id: "Gravenrode", name: "Gravenrode (SG-Gräfrath)" },
{ id: "Lennep", name: "Lennep (RS-Lennep)" },
{ id: "Silva", name: "Silva (SG-Wald)" },
{ id: "Sugambrer", name: "Sugambrer (SG-Höhscheid)" },
{ id: "Tenkterer", name: "Tenkterer (SG-Löhdorf)" },
{ id: "von Berg", name: "von Berg (SG-Ohligs)" },
],
submit() {
var _self = this;
this.loading = true;
var promise = fetch(window.location.href, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-WINTER-REQUEST-HANDLER": this.submitRequest,
"X-WINTER-REQUEST-PARTIALS": [],
"X-Requested-With": "XMLHttpRequest",
},
body: JSON.stringify(this.data),
});
promise.then(function (response) {
_self.loading = false;
if (response.status === 422) {
response.json().then((errors) => {
Object.keys(errors).forEach((field) => {
toasted.error(errors[field].join("<br>"));
});
});
}
if (response.status === 201) {
_self.finished = true;
}
});
},
};
}

67
components/Confirm.php Normal file
View File

@ -0,0 +1,67 @@
<?php
namespace Zoomyboy\Owncloud\Components;
use Cms\Classes\ComponentBase;
use GuzzleHttp\Client;
use Mail;
use Zoomyboy\Owncloud\Models\Settings;
class Confirm extends ComponentBase
{
/**
* Gets the details for the component.
*/
public function componentDetails()
{
return [
'name' => 'Confirm Component',
'description' => 'No description provided yet...',
];
}
public function onRun(): void
{
$b = base64_decode($this->property('payload'));
if (!$b) {
return;
}
$b = json_decode($b, true);
if (!$b) {
return;
}
$password = str_random(32);
$auth = base64_encode(Settings::get('username').':'.Settings::get('password'));
$client = new Client(['base_uri' => Settings::get('url'), 'headers' => ['Authorization' => 'Basic '.$auth, 'Content-Type' => 'application/x-www-form-urlencoded']]);
$client->post('/ocs/v1.php/cloud/users', [
'form_params' => [
'groups' => array_merge($b['oc_groups'], Settings::get('force_groups')),
'userid' => $b['username'],
'password' => $password,
],
]);
Mail::send('owncloud_confirm', [
'password' => $password,
'payload' => $b,
], function ($message) use ($b) {
$message->to($b['email'], $b['firstname'].' '.$b['lastname']);
});
}
/**
* Returns the properties provided by the component.
*/
public function defineProperties()
{
return [
'payload' => [
'label' => 'Payload',
],
];
}
}

118
components/Form.php Normal file
View File

@ -0,0 +1,118 @@
<?php
namespace Zoomyboy\Owncloud\Components;
use Cache;
use Cms\Classes\ComponentBase;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Lang;
use Illuminate\Validation\Rule;
use Input;
use Mail;
use Winter\Storm\Support\Facades\Validator;
use Zoomyboy\Owncloud\Models\Settings;
class Form extends ComponentBase
{
public array $groups = [];
/**
* Gets the details for the component.
*/
public function componentDetails()
{
return [
'name' => 'Form Component',
'description' => 'No description provided yet...',
];
}
/**
* Returns the properties provided by the component.
*/
public function defineProperties()
{
return [];
}
public function onRun(): void
{
$groups = Cache::remember('oc_groupsa', 1, function () {
return $this->getGroups();
});
$this->groups = collect($groups)
->filter(fn ($g) => !in_array($g, Settings::get('force_groups')))
->filter(fn ($g) => !in_array($g, Settings::get('disallowed_groups')))
->map(fn ($group) => ['id' => $group, 'name' => $group])
->toArray();
}
public function onSubmit()
{
$rules = [
'firstname' => 'required|string|max:255',
'lastname' => 'required|string|max:255',
'username' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9]+$/', Rule::notIn($this->getUsers())],
'email' => 'required|email|string|max:255',
'group' => 'required|string|max:255',
'function' => 'required|string|max:255',
'oc_groups' => 'present|array',
'oc_groups.*' => 'string',
];
$validator = Validator::make(Input::all(), $rules, [
'username.not_in' => 'OwnCloud Nutzername wird bereits verwendet.',
'username.regex' => 'OwnCloud Nutzername darf nur aus Buchstaben und Zahlen bestehen',
], Lang::get('zoomyboy.owncloud::validation.attributes'));
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$payload = base64_encode(json_encode($validator->validated()));
Mail::send('owncloud_request', [
'link' => url('/owncloud-confirm/'.$payload),
'payload' => $validator->validated(),
], function ($message) {
foreach (Settings::get('admins') as $admin) {
$message->to($admin);
}
});
dd('I');
return response()->json([], 201);
}
private function getGroups(): array
{
$response = $this->getClient()->get('/ocs/v1.php/cloud/groups');
$r = [];
$xml = simplexml_load_string((string) $response->getBody());
foreach ($xml->xpath('/ocs/data/groups/element') as $element) {
$r[] = (string) $element;
}
return $r;
}
private function getUsers(): array
{
$response = $this->getClient()->get('/ocs/v1.php/cloud/users');
$r = [];
$xml = simplexml_load_string((string) $response->getBody());
foreach ($xml->xpath('/ocs/data/users/element') as $element) {
$r[] = (string) $element;
}
return $r;
}
private function getClient(): Client
{
$auth = base64_encode(Settings::get('username').':'.Settings::get('password'));
return new Client(['base_uri' => Settings::get('url'), 'headers' => ['Authorization' => 'Basic '.$auth]]);
}
}

View File

@ -0,0 +1 @@
Zugang wurde erstellt.

View File

@ -0,0 +1,3 @@
<p>This is the default markup for component Form</p>
<small>You can delete this file if you want</small>

22
lang/de/lang.php Normal file
View File

@ -0,0 +1,22 @@
<?php
return [
'plugin' => [
'name' => 'owncloud',
'description' => 'No description provided yet...',
],
'permissions' => [
'some_permission' => 'Some permission',
],
'models' => [
'general' => [
'id' => 'ID',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
],
'request' => [
'label' => 'Request',
'label_plural' => 'Requests',
],
],
];

12
lang/en/validation.php Normal file
View File

@ -0,0 +1,12 @@
<?php
return [
'attributes' => [
'firstname' => 'Vorname',
'username' => 'OwnCloud Nutzername',
'lastname' => 'Nachname',
'email' => 'E-Mail-Adresse',
'group' => 'Stamm',
'function' => 'Funktion',
],
];

74
models/Request.php Normal file
View File

@ -0,0 +1,74 @@
<?php namespace Zoomyboy\Owncloud\Models;
use Model;
/**
* Request Model
*/
class Request extends Model
{
use \Winter\Storm\Database\Traits\Validation;
/**
* @var string The database table used by the model.
*/
public $table = 'zoomyboy_owncloud_requests';
/**
* @var array Guarded fields
*/
protected $guarded = ['*'];
/**
* @var array Fillable fields
*/
protected $fillable = [];
/**
* @var array Validation rules for attributes
*/
public $rules = [];
/**
* @var array Attributes to be cast to native types
*/
protected $casts = [];
/**
* @var array Attributes to be cast to JSON
*/
protected $jsonable = [];
/**
* @var array Attributes to be appended to the API representation of the model (ex. toArray())
*/
protected $appends = [];
/**
* @var array Attributes to be removed from the API representation of the model (ex. toArray())
*/
protected $hidden = [];
/**
* @var array Attributes to be cast to Argon (Carbon) instances
*/
protected $dates = [
'created_at',
'updated_at',
];
/**
* @var array Relations
*/
public $hasOne = [];
public $hasMany = [];
public $hasOneThrough = [];
public $hasManyThrough = [];
public $belongsTo = [];
public $belongsToMany = [];
public $morphTo = [];
public $morphOne = [];
public $morphMany = [];
public $attachOne = [];
public $attachMany = [];
}

32
models/Settings.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace Zoomyboy\Owncloud\Models;
use Model;
class Settings extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
public $settingsCode = 'zoomyboy_owncloud_settings';
public $settingsFields = 'fields.yaml';
public $settingsCacheTtl = 3600;
public function groups(): array
{
return [
];
}
public function users(): array
{
return [
];
}
public function createUser(): void
{
}
}

View File

@ -0,0 +1,19 @@
# ===================================
# List Column Definitions
# ===================================
columns:
id:
label: 'zoomyboy.owncloud::lang.models.general.id'
searchable: true
created_at:
label: 'zoomyboy.owncloud::lang.models.general.created_at'
type: datetime
searchable: true
sortable: true
invisible: true
updated_at:
label: 'zoomyboy.owncloud::lang.models.general.updated_at'
type: datetime
searchable: true
sortable: true

View File

@ -0,0 +1,8 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
id:
label: 'zoomyboy.owncloud::lang.models.general.id'
disabled: true

View File

@ -0,0 +1,20 @@
fields:
url:
label: URL
username:
label: Benutzername
password:
label: Passwort
force_groups:
label: Diese Gruppen immer zuweisen
type: taglist
mode: array
disallowed_groups:
label: Diese Gruppen niccht erlauben
type: taglist
mode: array
admins:
label: Anfrage an diese E-Mail-Adressen
type: taglist
mode: array

2
updates/version.yaml Normal file
View File

@ -0,0 +1,2 @@
'1.0.0':
- 'First version of owncloud'

88
views/macros.htm Normal file
View File

@ -0,0 +1,88 @@
{% macro field(context, name, label, required, type) %}
<label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex">
<input name="{{name}}" type="{{type|default('text')}}" id="{{name}}" placeholder=" " class="bg-white rounded-lg focus:outline-none text-gray-600 text-left placeholder-white peer py-1 px-2 sm:py-2 text-sm sm:text-base sm:px-3 w-full" x-model="data.{{name}}" />
<span
class="transition-all duration-200 absolute text-gray-600 left-2 flex bg-white items-center -top-3 px-1 peer-placeholder-shown:bottom-0 peer-placeholder-shown:-top-0
text-xs xs:text-sm peer-placeholder-shown:text-sm xs:peer-placeholder-shown:text-base peer-focus:text-xs xs:peer-focus:text-sm
peer-focus:-top-3 peer-focus:bottom-auto "
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
</label>
{% endmacro %}
{% macro textarea(context, name, label, required, type) %}
<label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex">
<textarea name="{{name}}" rows="6" id="{{name}}" class="bg-white rounded-lg focus:outline-none text-gray-600 text-left placeholder-white peer py-1 px-2 sm:py-2 text-sm sm:text-base sm:px-3 w-full" x-model="data.{{name}}">
</textarea>
<span
class="transition-all duration-200 absolute text-gray-600 left-2 flex bg-white items-center -top-3 px-1 text-xs xs:text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
</label>
{% endmacro %}
{% macro select(context, name, label, required, options) %}
<label class="w-full border border-solid border-gray-500 focus-within:border-primary rounded-lg relative flex" for="{{name}}">
<select name="{{name}}" id="{{name}}" class="bg-white rounded-lg focus:outline-none text-gray-600 text-left peer py-1 px-2 sm:py-2 text-sm sm:text-base sm:px-3 w-full" x-model="data.{{name}}">
<option :selected="data.{{name}} === ''" value="">-- kein --</option>
<template x-for="model in {{options}}">
<option :selected="data.{{name}} === model.id" :value="model.id" x-text="model.name"></option>
</template>
</select>
<span
class="absolute text-gray-600 left-2 flex bg-white items-center -top-3 px-1 text-xs xs:text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
</label>
{% endmacro %}
{% macro radio(context, name, label, required, options) %}
<div>
<span
class="text-gray-600 text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
<div class="grid grid-cols-2 sm:grid-cols-1 gap-2">
<template x-for="option, index in {{options}}">
<label :for="`{{name}}-${index}`" class="block relative flex items-center">
<input type="radio" name="{{name}}" :value="option.name" x-model="data.{{name}}" class="peer absolute invisible" :id="`{{name}}-${index}`" />
<span class="border-neutral-400 border-4 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 rounded-full block"></span>
<span class="peer-checked:bg-primary left-2 w-2 h-2 absolute rounded-full block"></span>
<span class="pl-8" x-text="option.name"></span>
</label>
</template>
</div>
</div>
{% endmacro %}
{% macro yesno(context, name, label, required) %}
<div>
<div class="flex flex-col space-y-1">
<label for="{{name}}" class="block relative flex items-center">
<input type="checkbox" name="{{name}}" x-model="data.{{name}}" class="peer absolute invisible" id="{{name}}" />
<span class="border-neutral-400 border-4 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 rounded block"></span>
<span class="peer-checked:bg-primary left-2 w-2 h-2 absolute rounded-sm block"></span>
<span class="pl-8 text-sm">{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span>
</label>
</div>
</div>
{% endmacro %}
{% macro checkboxes(context, name, label, required, options, settings) %}
<div>
<span
class="text-gray-600 flex text-sm"
>{{label}} {% if required %} <span class="text-red-800 ml-1">*</span> {% endif %}</span
>
<div class="mt-2 grid gap-2 grid-cols-[repeat(auto-fill,minmax(150px,1fr))]">
<template x-for="option, index in {{options}}">
<label :for="`{{name}}-${index}`" class="block relative flex items-center">
<input type="checkbox" name="{{name}}[]" :value="option.id" x-model="data.{{name}}" {% if settings.after %} @change="{{settings.after}}" {% endif %} class="peer absolute invisible" :id="`{{name}}-${index}`" />
<span class="border-neutral-400 border-4 border-solid peer-checked:border-primary absolute left-0 w-6 h-6 rounded block"></span>
<span class="peer-checked:bg-primary left-2 w-2 h-2 absolute rounded-sm block"></span>
<span class="pl-8 text-sm sm:text-base flex flex-col">
<span x-text="option.name" class="block" class="leading-none"></span>
<template x-if="option.hint">
<span class="text-xs leading-none text-gray-500" x-text="option.hint"></span>
</template>
</span>
</label>
</template>
</div>
</div>
{% endmacro %}