Skip to content

Commit

Permalink
Merge branch 'master' into hub-email-verification
Browse files Browse the repository at this point in the history
  • Loading branch information
emmachughes authored Jan 30, 2025
2 parents 3b6dfe6 + 6b00c0b commit a216d12
Show file tree
Hide file tree
Showing 25 changed files with 305 additions and 46 deletions.
6 changes: 3 additions & 3 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ v1.0.1, March 2022

## Purpose

At Cerpus, we believe education can make the world a better place. In adopting this Community Code of Conduct, as Cerpus employees, we are committed to fostering a welcoming environment for collaboration, creativity, and the free exchange of ideas for developing, maintaining, and using open-source educational software. We aim to empower all participants to actively engage and help build a friendly and safe Cerpus open-source developer community.
At Edlib, we believe education can make the world a better place. In adopting this Community Code of Conduct, as Edlib employees, we are committed to fostering a welcoming environment for collaboration, creativity, and the free exchange of ideas for developing, maintaining, and using open-source educational software. We aim to empower all participants to actively engage and help build a friendly and safe Edlib open-source developer community.

Whether publicly or privately, and whether in-person or online, we expect all members of this community to interact both professionally and without harassment toward others, regardless of race, color, creed, gender, gender identity, religion, marital status, domestic partner status, genetic information, age, national origin or ancestry, military or veteran status, sexual orientation, or either physical or mental disability.

Expand Down Expand Up @@ -37,5 +37,5 @@ Repository maintainers reserve the right to remove offensive content. You can co
## Consequences

Violations of this Code of Conduct may result in:
* Disqualification from Cerpus Events and Conferences
* Being blocked from Cerpus's [GitHub Organization](https://github.com/cerpus)
* Disqualification from Edlib Events and Conferences
* Being blocked from Edlib's [GitHub Organization](https://github.com/cerpus)
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to the Edlib Open Source Project

Cerpus welcomes contributions to our [open source projects on Github][1]. You
Edlib welcomes contributions to our [open source projects on Github][1]. You
can [learn more about contributing][2] on our documentation website.

[1]: https://github.com/cerpus/
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Edlib is an [open-source](https://github.com/cerpus/Edlib/blob/master/LICENSE) a
* Insert content into a context within your learning system or point directly to the specific content using links.
* All resources are stored and available through a single repository for your learning content - neat and tidy.

> The individual learner is at the center of everything we do, and our vision is to provide first-class open learning tools and content to as many of those individuals as possible. — Tommy W. Nordeng (co-founder of Cerpus)
> The individual learner is at the center of everything we do, and our vision is to provide first-class open learning tools and content to as many of those individuals as possible. — Tommy W. Nordeng (co-founder of Edlib AS)

## Installation
Expand Down
10 changes: 10 additions & 0 deletions docs/docs/developers/content-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ support this, content selected via the default endpoint just replaces the previo

Make the 'Use Content' button in item selections return a link to the current version of a resource.

* `ext_edlib3_include_owner_info`

If enabled, and the LTI Platform has setting `The platform authorizes edit access` enabled, the e-mail address of
the content owner will be included in the response. Set to `"1"` to enable, not enabled by default.

* `ext_edlib3_copy_before_save`

For edit requests. Instructs Edlib to create a copy of the content and save the changes to the copy.
Set to `"1"` to enable, not enabled by default.

## Extended LTI parameters sent to LTI tools by the Hub

* `ext_edlib3_embed_resize_code`
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/developers/contributing/contributing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sidebar_position: 1

# Contributing

Edlib AS welcomes contributions to our [open source projects on Github](https://github.com/cerpus/Edlib). When contributing, please follow the [Cerpus Community Code of Conduct](https://github.com/cerpus/Edlib/blob/master/CODE_OF_CONDUCT.md).
Edlib AS welcomes contributions to our [open source projects on Github](https://github.com/cerpus/Edlib). When contributing, please follow the [Edlib Community Code of Conduct](https://github.com/cerpus/Edlib/blob/master/CODE_OF_CONDUCT.md).

## Issues

Expand Down
1 change: 0 additions & 1 deletion docs/src/pages/contact-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ Connect with one of our offices (in Norway).
### Alsvåg Office

**Cerpus Learning Garden**<br/>

Alsvågveien 688<br/>
8432 Alsvåg<br/>
Norway<br/>
Expand Down
13 changes: 6 additions & 7 deletions sourcecode/apis/contentauthor/.php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<?php

$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->path('app')
->path('bootstrap/app.php')
->path('config')
->path('database')
->path('routes')
->path('tests')
->in(__DIR__ . '/app/')
->in(__DIR__ . '/bootstrap/')->exclude('cache')->name('/app.php')
->in(__DIR__ . '/config/')
->in(__DIR__ . '/database/')
->in(__DIR__ . '/routes/')
->in(__DIR__ . '/tests/')
;

return (new PhpCsFixer\Config())
Expand Down
44 changes: 44 additions & 0 deletions sourcecode/hub/app/Console/Commands/AddLtiToolExtra.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Models\LtiTool;
use App\Models\LtiToolExtra;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

use function is_string;

class AddLtiToolExtra extends Command
{
protected $signature = <<<'EOF'
edlib:add-lti-tool-extra
{parent : ID or slug of the parent tool}
{name : The name of the extra}
{url : The launch URL for the extra}
{--admin : The extra is for admins only}
{--slug= : An optional slug for the extra}
EOF;

public function handle(): void
{
$tool = LtiTool::where('id', $this->argument('parent'))
->orWhere('slug', $this->argument('parent'))
->firstOrFail();

DB::transaction(function () use ($tool) {
$slug = $this->option('slug');
$extra = new LtiToolExtra();
$extra->name = $this->argument('name');
$extra->lti_launch_url = $this->argument('url');
$extra->admin = $this->option('admin');
if (is_string($slug)) {
$extra->slug = $slug;
}
$extra->lti_tool_id = $tool->id;
$extra->save();
});
}
}
16 changes: 14 additions & 2 deletions sourcecode/hub/app/Http/Controllers/Admin/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
use App\Jobs\RebuildContentIndex;
use App\Models\LtiToolExtra;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\Response;

use function back;
use function response;
use function route;
use function trans;
use function view;

final class AdminController extends Controller
Expand All @@ -23,10 +27,18 @@ public function index(): View
]);
}

public function rebuildContentIndex(Dispatcher $dispatcher): RedirectResponse
public function rebuildContentIndex(Dispatcher $dispatcher, Request $request): Response
{
$dispatcher->dispatch(new RebuildContentIndex());

$request->session()
->flash('alert', trans('messages.alert-rebuilding-content-index'));

if ($request->header('HX-Request')) {
return response()->noContent()
->header('HX-Redirect', route('admin.index'));
}

return back()
->with('alert', trans('messages.alert-rebuilding-content-index'));
}
Expand Down
24 changes: 24 additions & 0 deletions sourcecode/hub/app/Http/Controllers/Admin/ContextController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

namespace App\Http\Controllers\Admin;

use App\Http\Requests\AttachContextToContentsRequest;
use App\Http\Requests\StoreContextRequest;
use App\Jobs\AttachContextToContents;
use App\Models\Context;
use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;

Expand All @@ -31,4 +34,25 @@ public function add(StoreContextRequest $request): RedirectResponse
return redirect()->route('admin.contexts.index')
->with('alert', trans('messages.context-added'));
}

public function attachToContents(): Response
{
$contexts = Context::all()
->mapWithKeys(fn(Context $context) => [$context->id => $context->name]);

return response()->view('admin.contexts.attach-to-contents', [
'available_contexts' => $contexts,
]);
}

public function performAttachToContents(
AttachContextToContentsRequest $request,
Dispatcher $dispatcher,
): RedirectResponse {
$dispatcher->dispatch(
new AttachContextToContents($request->getContext()),
);

return redirect()->route('admin.index');
}
}
8 changes: 3 additions & 5 deletions sourcecode/hub/app/Http/Controllers/ContentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,12 @@ public function use(
?? throw new BadMethodCallException('Not in LTI selection context');
assert(is_string($returnUrl));

$credentials = LtiPlatform::where('key', $request->session()->get('lti.oauth_consumer_key'))
->firstOrFail()
->getOauth1Credentials();
$platform = LtiPlatform::where('key', $request->session()->get('lti.oauth_consumer_key'))->firstOrFail();

$ltiRequest = $itemSelectionFactory->createItemSelection(
[$version->toLtiLinkItem()],
[$version->toLtiLinkItem($platform)],
$returnUrl,
$credentials,
$platform->getOauth1Credentials(),
$request->session()->get('lti.data'),
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace App\Http\Requests;

use App\Models\Context;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class AttachContextToContentsRequest extends FormRequest
{
/**
* @return array<mixed>
*/
public function rules(): array
{
return [
'context' => ['required', Rule::exists(Context::class, 'id')],
];
}

public function getContext(): Context
{
return Context::where('id', $this->validated('context'))->firstOrFail();
}
}
7 changes: 4 additions & 3 deletions sourcecode/hub/app/Http/Requests/ContentFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ private function attachModel(array $hits, bool $forUser, bool $showDrafts): Coll
$eagerLoad = ['users'];
if ($showDrafts) {
$eagerLoad[] = 'latestVersion';
} else {
}
if (!$showDrafts || $forUser) {
$eagerLoad[] = 'latestPublishedVersion';
}

Expand All @@ -331,7 +332,7 @@ private function attachModel(array $hits, bool $forUser, bool $showDrafts): Coll
?? throw new NotFoundHttpException();

$canUse = Gate::allows('use', [$model, $version]);
$canEdit = Gate::allows('edit', $model);
$canEdit = Gate::allows('edit', [$model, $version]);
$canView = Gate::allows('view', $model);
$canDelete = $forUser && Gate::allows('delete', $model);
$canCopy = Gate::allows('copy', $model);
Expand All @@ -349,7 +350,7 @@ private function attachModel(array $hits, bool $forUser, bool $showDrafts): Coll
useUrl: $canUse ? route('content.use', [$model, $version]) : null,
editUrl: $canEdit ? route('content.edit', [$model, $version]) : null,
shareUrl: $canView ? route('content.share', [$model, SessionScope::TOKEN_PARAM => null]) : null,
copyUrl: $canCopy ? route('content.copy', [$model, $version]) : null,
copyUrl: $canCopy ? route('content.copy', [$model]) : null,
deleteUrl: $canDelete ? route('content.delete', [$model]) : null,
);
});
Expand Down
29 changes: 29 additions & 0 deletions sourcecode/hub/app/Jobs/AttachContextToContents.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\Jobs;

use App\Models\Content;
use App\Models\Context;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\SerializesModels;

class AttachContextToContents implements ShouldQueue, ShouldBeUnique
{
use Dispatchable;
use Queueable;
use SerializesModels;

public function __construct(private readonly Context $context) {}

public function handle(): void
{
Content::lazy()->each(function (Content $content) {
$content->contexts()->syncWithoutDetaching([$this->context]);
});
}
}
27 changes: 20 additions & 7 deletions sourcecode/hub/app/Models/ContentVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Models;

use App\Enums\ContentRole;
use App\Events\ContentVersionDeleting;
use App\Events\ContentVersionSaving;
use App\Lti\ContentItemSelectionFactory;
Expand All @@ -26,6 +27,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;

use function app;
use function assert;
Expand Down Expand Up @@ -80,9 +82,16 @@ class ContentVersion extends Model
'saving' => ContentVersionSaving::class,
];

public function toLtiLinkItem(): EdlibLtiLinkItem
public function toLtiLinkItem(LtiPlatform $platform): EdlibLtiLinkItem
{
$iconUrl = $this->icon?->getUrl();
$ownerEmail = null;

if (Session::get('lti.ext_edlib3_include_owner_info') === '1' && $platform->authorizes_edit) {
$ownerEmail = $this->content?->users->first(
fn(User $user) => $user->getRelationValue('pivot')->role === ContentRole::Owner,
)?->email;
}

return (new EdlibLtiLinkItem(
title: $this->getTitle(),
Expand All @@ -96,6 +105,7 @@ public function toLtiLinkItem(): EdlibLtiLinkItem
->withLanguageIso639_3($this->language_iso_639_3)
->withLicense($this->license)
->withTags($this->getSerializedTags())
->withOwnerEmail($ownerEmail)
;
}

Expand Down Expand Up @@ -134,14 +144,17 @@ public function toItemSelectionRequest(): Oauth1Request
?? throw new BadMethodCallException('Not in LTI selection context');
assert(is_string($returnUrl));

$credentials = LtiPlatform::where('key', session()->get('lti.oauth_consumer_key'))
->firstOrFail()
->getOauth1Credentials();

$platform = LtiPlatform::where('key', session()->get('lti.oauth_consumer_key'))->firstOrFail();
$data = session()->get('lti.data');

return app()->make(ContentItemSelectionFactory::class)
->createItemSelection([$this->toLtiLinkItem()], $returnUrl, $credentials, $data);
return app()
->make(ContentItemSelectionFactory::class)
->createItemSelection(
[$this->toLtiLinkItem($platform)],
$returnUrl,
$platform->getOauth1Credentials(),
$data,
);
}

/**
Expand Down
Loading

0 comments on commit a216d12

Please sign in to comment.