Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions category.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public function definition() {

$id = optional_param('id', 0, PARAM_INT);
$category = $DB->get_record_sql('
SELECT c.id, c.name, c.pid, c.internshare, c.shareall, c.iconmerge
SELECT c.id, c.name, c.pid, c.internshare, c.shareall, c.iconmerge, c.externaccess, c.hash
FROM {block_exaportcate} c
WHERE c.userid = ? AND id = ?
', array($USER->id, $id));
Expand All @@ -172,6 +172,8 @@ public function definition() {
$category->shareall = 0;
$category->id = 0;
$category->iconmerge = 0;
$category->externaccess = 0;
$category->hash = '';
};

// Don't forget the underscore!
Expand Down Expand Up @@ -260,6 +262,27 @@ public function definition() {
$mform->addElement('html', '</td></tr>');
$mform->addElement('html', '<tr id="internaccess-groups"><td></td>' .
'<td><div id="sharing-grouplist">grouplist</div></td></tr>');

// External access (read-only link for non-logged-in users) — inside sharing submenu.
if (block_exaport_externaccess_enabled()
&& has_capability('block/exaport:shareextern', context_system::instance())) {
$mform->addElement('html', '<tr><td colspan="2"><hr style="margin: 8px 0;" /></td></tr>');
$mform->addElement('html', '<tr><td>');
$mform->addElement('html', '<input type="checkbox" name="externaccess" value="1"' .
($category->externaccess ? ' checked="checked"' : '') . '/>');
$mform->addElement('html', '</td><td>' . get_string('externalaccess', 'block_exaport') . '</td></tr>');

// Show the external link if already enabled and category exists.
if ($category->id > 0 && $category->externaccess && $category->hash) {
$url = block_exaport_get_external_category_url($category, $USER->id);
$mform->addElement('html', '<tr id="externaccess-category-settings"><td></td><td>' .
'<div style="padding: 4px;"><a href="' . s($url) . '" target="_blank">' . s($url) . '</a></div>' .
'<div class="alert alert-info" style="margin-top: 5px;">' .
get_string('externaccess_category_readonly', 'block_exaport') .
'</div></td></tr>');
}
}

$mform->addElement('html', '</table></div>');
$mform->addElement('html', '</div></div>');
};
Expand Down Expand Up @@ -292,6 +315,24 @@ public function validation($data, $files) {
$newentry->internshare = 0;
}

// Handle external access.
if (block_exaport_externaccess_enabled()
&& has_capability('block/exaport:shareextern', context_system::instance())) {
$newentry->externaccess = optional_param('externaccess', 0, PARAM_INT) ? 1 : 0;
} else {
$newentry->externaccess = 0;
}

// Generate hash for category if not yet set.
if ($newentry->externaccess) {
$existingcat = $newentry->id ? $DB->get_record('block_exaportcate', ['id' => $newentry->id], 'hash') : null;
if (!$existingcat || empty($existingcat->hash)) {
do {
$newentry->hash = md5(random_bytes(16));
} while ($DB->record_exists("block_exaportcate", array("hash" => $newentry->hash)));
}
}

if ($newentry->id) {
// keep creatorid as is.. not "updatedby" but "CREATORid" so keep it
$DB->update_record("block_exaportcate", $newentry);
Expand Down Expand Up @@ -430,7 +471,7 @@ public function validation($data, $files) {
$category = null;
if ($id = optional_param('id', 0, PARAM_INT)) {
$category = $DB->get_record_sql('
SELECT c.id, c.name, c.pid, c.internshare, c.shareall, c.iconmerge
SELECT c.id, c.name, c.pid, c.internshare, c.shareall, c.iconmerge, c.externaccess, c.hash
FROM {block_exaportcate} c
WHERE c.userid = ? AND id = ?
', array($USER->id, $id));
Expand Down
3 changes: 3 additions & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
<FIELD NAME="structure_share" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Sharing category as structure (work with structure_shareall field"/>
<FIELD NAME="iconmerge" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="try to merge thumbnail with the folder icon"/>
<FIELD NAME="creatorid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="User ID of the creator of this category"/>
<FIELD NAME="externaccess" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="external access enabled for this category"/>
<FIELD NAME="hash" TYPE="char" LENGTH="32" NOTNULL="false" SEQUENCE="false" COMMENT="hash for external access URL"/>
</FIELDS>
<KEYS>
<KEY NAME="id" TYPE="primary" FIELDS="id" COMMENT="primary key for bookmark categories"/>
Expand All @@ -62,6 +64,7 @@
<INDEX NAME="internshare" UNIQUE="false" FIELDS="internshare"/>
<INDEX NAME="structure_shareall" UNIQUE="false" FIELDS="structure_shareall"/>
<INDEX NAME="structure_share" UNIQUE="false" FIELDS="structure_share"/>
<INDEX NAME="hash" UNIQUE="false" FIELDS="hash"/>
</INDEXES>
</TABLE>
<TABLE NAME="block_exaportcatshar" COMMENT="information to which categories is shared to other users">
Expand Down
26 changes: 26 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -1326,5 +1326,31 @@ function block_exaport_wrong_personal_information_upgrade_2012120301($matches) {
upgrade_block_savepoint(true, 2026022404, 'exaport');
}

if ($oldversion < 2026032501) {
// Add externaccess field to block_exaportcate table.
$table = new xmldb_table('block_exaportcate');
$field = new xmldb_field('externaccess', XMLDB_TYPE_INTEGER, '3', null, null, null, '0', 'creatorid');

if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Add hash field to block_exaportcate table.
$field = new xmldb_field('hash', XMLDB_TYPE_CHAR, '32', null, null, null, null, 'externaccess');

if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}

// Add index on hash field.
$index = new xmldb_index('hash', XMLDB_INDEX_NOTUNIQUE, array('hash'));
if (!$dbman->index_exists($table, $index)) {
$dbman->add_index($table, $index);
}

// Exaport savepoint reached.
upgrade_block_savepoint(true, 2026032501, 'exaport');
}

return $result;
}
7 changes: 7 additions & 0 deletions lang/en/block_exaport.php
Original file line number Diff line number Diff line change
Expand Up @@ -977,3 +977,10 @@
$string['no_views_to_distribute'] = 'No view template defined to distribute';
$string['views_created'] = 'Views created: {$a}';
$string['views_skipped'] = 'Views skipped (already exist): {$a}';

// Shared category external access.
$string['externaccess_category_readonly'] = 'This link allows read-only access. Visitors can only view the category contents - no editing, uploading, or deleting is possible.';
$string['shared_category_readonly'] = 'You are viewing a shared category. This is a read-only view.';
$string['shared_category_notfound'] = 'Shared category not found or access denied.';
$string['shared_category'] = 'Shared Category';

97 changes: 97 additions & 0 deletions lib/sharelib.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,67 @@ function block_exaport_get_external_view_url(stdClass $view, $userid = -1) {
return $CFG->wwwroot . '/blocks/exaport/shared_view.php?access=hash/' . $userid . '-' . $view->hash;
}

/**
* Generate external URL for a shared category.
*
* @param stdClass $category Category object with hash field
* @param int $userid Owner user ID (-1 for current user)
* @return string The external URL
*/
function block_exaport_get_external_category_url(stdClass $category, $userid = -1) {
global $CFG, $USER;
if ($userid == -1) {
$userid = $USER->id;
}
return $CFG->wwwroot . '/blocks/exaport/shared_category.php?access=hash/' . $userid . '-' . $category->hash;
}

/**
* Get a category from an external access string (hash-based).
* Returns the category if access is valid, null otherwise.
* This is strictly read-only access.
*
* @param string $access Access string in format "hash/<userid>-<hash>"
* @return stdClass|null Category object with access info, or null
*/
function block_exaport_get_category_from_access($access) {
global $DB;

$accesspath = explode('/', $access);
if (count($accesspath) != 2) {
return null;
}

if ($accesspath[0] !== 'hash') {
return null;
}

$hash = $accesspath[1];
$hash = explode('-', $hash);

if (count($hash) != 2) {
return null;
}

$userid = clean_param($hash[0], PARAM_INT);
$hash = clean_param($hash[1], PARAM_ALPHANUM);

if (empty($userid) || empty($hash)) {
return null;
}

// Look up the category by userid, hash, and externaccess flag.
$conditions = array("userid" => $userid, "hash" => $hash, "externaccess" => 1);
if (!$category = $DB->get_record("block_exaportcate", $conditions)) {
return null;
}

$category->access = new stdClass();
$category->access->request = 'extern';

return $category;
}

function block_exaport_get_user_from_access($access, $epopaccess = false) {
global $DB;

Expand Down Expand Up @@ -362,6 +423,42 @@ function block_exaport_get_item($itemid, $access, $epopaccess = false, $pdfacces
$item->allowComments = true;
$item->showComments = true;
}
} else if (preg_match('!^category/(.+)$!', $access, $matches)) {
// External category access mode (read-only).
if (!$category = block_exaport_get_category_from_access($matches[1])) {
return;
}

// Verify the item belongs to the category owner.
$conditions = array("id" => $itemid, "userid" => $category->userid);
if (!$item = $DB->get_record("block_exaportitem", $conditions)) {
return;
}

// Verify the item is in this category or a subcategory of it.
$incategory = false;
$checkcat = $item->categoryid;
$maxdepth = 50;
while ($checkcat && $maxdepth-- > 0) {
if ($checkcat == $category->id) {
$incategory = true;
break;
}
$parentcat = $DB->get_field('block_exaportcate', 'pid', ['id' => $checkcat, 'userid' => $category->userid]);
if (!$parentcat) {
break;
}
$checkcat = $parentcat;
}
if (!$incategory) {
return;
}

$item->access = $category->access;
$item->access->page = 'category';
// Strictly read-only: no comments at all.
$item->allowComments = false;
$item->showComments = false;
} else {
return;
}
Expand Down
Loading