Skip to content

Commit 2d71ecb

Browse files
committed
Add "Link to the file" and "Create an access controlled link to the file" options when adding files using repository_office365 plugin
1 parent cb1b07c commit 2d71ecb

File tree

5 files changed

+492
-26
lines changed

5 files changed

+492
-26
lines changed

local/o365/classes/rest/unified.php

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,163 @@ public function get_sharing_link(string $fileid, string $o365userid): string {
18301830
return $response['link']['webUrl'];
18311831
}
18321832

1833+
/**
1834+
* Upload a file to OneDrive using createUploadSession API.
1835+
*
1836+
* @param string $o365userid The user's O365 user ID.
1837+
* @param string $filepath The local path to the file to upload.
1838+
* @param string $filename The name of the new file.
1839+
* @param string $parentid The parent folder ID (optional, defaults to root).
1840+
* @return string The uploaded file's ID.
1841+
* @throws moodle_exception
1842+
*/
1843+
public function upload_file_with_session(
1844+
string $o365userid,
1845+
string $filepath,
1846+
string $filename,
1847+
string $parentid = ''
1848+
): string {
1849+
// Create upload session.
1850+
$endpoint = "/users/$o365userid/drive/";
1851+
if (!empty($parentid)) {
1852+
$endpoint .= "items/$parentid:/" . urlencode($filename) . ":/createUploadSession";
1853+
} else {
1854+
$endpoint .= "root:/" . urlencode($filename) . ":/createUploadSession";
1855+
}
1856+
1857+
$behaviour = ['item' => ['@microsoft.graph.conflictBehavior' => 'rename']];
1858+
$sessionresponse = $this->apicall('post', $endpoint, json_encode($behaviour));
1859+
$session = $this->process_apicall_response($sessionresponse, ['uploadUrl' => null]);
1860+
1861+
if (empty($session['uploadUrl'])) {
1862+
throw new moodle_exception('errorwhilesharing', 'repository_office365');
1863+
}
1864+
1865+
// Upload the file content.
1866+
$filesize = filesize($filepath);
1867+
if ($filesize === false) {
1868+
throw new moodle_exception('errorwhiledownload', 'repository_office365');
1869+
}
1870+
1871+
// Prepare curl clients - one without auth, one with auth.
1872+
$curl = new \curl();
1873+
$authcurl = new \curl();
1874+
$authcurl->setHeader(['Authorization: Bearer ' . $this->token->get_token()]);
1875+
1876+
$options = ['file' => $filepath];
1877+
1878+
// Try each curl class in turn until we succeed.
1879+
// First attempt an upload with no auth headers (will work for personal onedrive accounts).
1880+
// If that fails, try an upload with the auth headers (will work for work onedrive accounts).
1881+
$curls = [$curl, $authcurl];
1882+
$response = null;
1883+
foreach ($curls as $curlinstance) {
1884+
$curlinstance->setHeader('Content-Length: ' . $filesize);
1885+
$curlinstance->setHeader('Content-Range: bytes 0-' . ($filesize - 1) . '/' . $filesize);
1886+
$response = $curlinstance->put($session['uploadUrl'], $options);
1887+
if ($curlinstance->errno == 0) {
1888+
$response = json_decode($response, true);
1889+
}
1890+
if (is_array($response) && !empty($response['id'])) {
1891+
// We can stop now - there is a valid file returned.
1892+
return $response['id'];
1893+
}
1894+
}
1895+
1896+
// If we get here, neither curl attempt succeeded.
1897+
throw new moodle_exception('errorwhilesharing', 'repository_office365');
1898+
}
1899+
1900+
/**
1901+
* Copy a OneDrive file by downloading and re-uploading it.
1902+
*
1903+
* @param string $fileid The source file id.
1904+
* @param string $o365userid The user's O365 user ID (for destination).
1905+
* @param string $newname The new file name (optional, defaults to original name with " - Shared" suffix).
1906+
* @param string $parentid The parent folder ID (optional, defaults to root).
1907+
* @return string The new file's ID.
1908+
* @throws moodle_exception
1909+
*/
1910+
public function copy_file(string $fileid, string $o365userid, string $newname = '', string $parentid = ''): string {
1911+
// Get file metadata including download URL.
1912+
$fileinfo = $this->get_file_metadata($fileid, $o365userid);
1913+
1914+
if (empty($fileinfo['@microsoft.graph.downloadUrl'])) {
1915+
throw new moodle_exception('errorwhiledownload', 'repository_office365');
1916+
}
1917+
1918+
// Use original filename if no new name specified.
1919+
if (empty($newname)) {
1920+
$newname = $fileinfo['name'];
1921+
}
1922+
1923+
// Download the file to a temporary location.
1924+
$tmpfilename = clean_param($fileid, PARAM_PATH);
1925+
$temppath = make_request_directory() . $tmpfilename;
1926+
1927+
// Download without auth headers (as per Graph API requirements).
1928+
$curl = new \curl();
1929+
$options = ['filepath' => $temppath, 'timeout' => 60, 'followlocation' => true, 'maxredirs' => 5];
1930+
$result = $curl->download_one($fileinfo['@microsoft.graph.downloadUrl'], null, $options);
1931+
1932+
if (!$result) {
1933+
throw new moodle_exception('errorwhiledownload', 'repository_office365');
1934+
}
1935+
1936+
// Upload to the destination.
1937+
$newfileid = $this->upload_file_with_session($o365userid, $temppath, $newname, $parentid);
1938+
1939+
// Clean up temp file.
1940+
@unlink($temppath);
1941+
1942+
return $newfileid;
1943+
}
1944+
1945+
/**
1946+
* Copy a group OneDrive file to a user's OneDrive by downloading and re-uploading it.
1947+
*
1948+
* @param string $groupid The group's O365 group ID.
1949+
* @param string $fileid The source file id in the group.
1950+
* @param string $o365userid The user's O365 user ID (for destination).
1951+
* @param string $newname The new file name (optional, defaults to original name).
1952+
* @return string The new file's ID in the user's OneDrive.
1953+
* @throws moodle_exception
1954+
*/
1955+
public function copy_group_file_to_user(string $groupid, string $fileid, string $o365userid, string $newname = ''): string {
1956+
// Get file metadata including download URL.
1957+
$fileinfo = $this->get_group_file_metadata($groupid, $fileid);
1958+
1959+
if (empty($fileinfo['@microsoft.graph.downloadUrl'])) {
1960+
throw new moodle_exception('errorwhiledownload', 'repository_office365');
1961+
}
1962+
1963+
// Use original filename if no new name specified.
1964+
if (empty($newname)) {
1965+
$newname = $fileinfo['name'];
1966+
}
1967+
1968+
// Download the file to a temporary location.
1969+
$tmpfilename = clean_param($fileid, PARAM_PATH);
1970+
$temppath = make_request_directory() . $tmpfilename;
1971+
1972+
// Download without auth headers (as per Graph API requirements).
1973+
$curl = new \curl();
1974+
$options = ['filepath' => $temppath, 'timeout' => 60, 'followlocation' => true, 'maxredirs' => 5];
1975+
$result = $curl->download_one($fileinfo['@microsoft.graph.downloadUrl'], null, $options);
1976+
1977+
if (!$result) {
1978+
throw new moodle_exception('errorwhiledownload', 'repository_office365');
1979+
}
1980+
1981+
// Upload to the user's OneDrive root.
1982+
$newfileid = $this->upload_file_with_session($o365userid, $temppath, $newname, '');
1983+
1984+
// Clean up temp file.
1985+
@unlink($temppath);
1986+
1987+
return $newfileid;
1988+
}
1989+
18331990
/**
18341991
* Get a specific user's information.
18351992
*

local/o365/version.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
defined('MOODLE_INTERNAL') || die();
2828

29-
$plugin->version = 2024100710;
29+
$plugin->version = 2024100710.01;
3030
$plugin->requires = 2024100700;
3131
$plugin->release = '4.5.2';
3232
$plugin->component = 'local_o365';

repository/office365/lang/en/repository_office365.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,74 @@
2828
$string['coursegroup'] = 'Disable Groups (Courses) folder in file picker';
2929
$string['defaultgroupsfolder'] = 'Course Files';
3030

31+
$string['enableanonymousshare'] = 'Disable "Create an access controlled link to the file" option';
32+
$string['enableanonymousshare_help'] = 'When unchecked (default), users can choose to create a copy of a file and share it with everyone in the organization. The copy is stored in the user\'s OneDrive and shared with all organization members.
33+
34+
**How It Works:**
35+
* Creates a copy of the selected file with a " - Shared" suffix.
36+
* The copy is saved to the user\'s OneDrive (not the original location).
37+
* An organization-scoped sharing link is created for the copy.
38+
* The original file remains unchanged with its existing permissions.
39+
40+
**Access Control:**
41+
* Only members of your Microsoft 365 organization can access the fil.e
42+
* Anyone in the organization with the link can VIEW the file.
43+
* External users and anonymous users CANNOT access the file.
44+
* The link does not expire automatically.
45+
* The file owner can manage or revoke sharing from their OneDrive.
46+
47+
**When to Use:**
48+
* You want to share a file with all organization members.
49+
* You want to protect the original file from accidental changes.
50+
* Storage space in Moodle is a concern.
51+
* The file content is appropriate for organization-wide access.
52+
53+
**Important Notes:**
54+
* The copy operation may take a few seconds for large files.
55+
* Users should ensure the file content is appropriate for organization-wide sharing.
56+
* The copied file will appear in the user\'s OneDrive with " - Shared" suffix.
57+
* Changes made to the original file will NOT be reflected in the shared copy, and vice versa.
58+
59+
Check this box to disable this option and prevent users from creating organization-shared copies.';
60+
$string['enableanonymoussharewarning'] = '<div class="alert alert-info"><strong>Note:</strong> When using the "Create an access controlled link to the file" option, a copy of the original file is created and shares with all organization members. The original file remains unchanged. Users should ensure the file content is appropriate for organization-wide access.</div>';
61+
62+
$string['enabledirectlink'] = 'Disable "Link to the file" option';
63+
$string['enabledirectlink_help'] = 'When unchecked (default), users can add a direct link to a file in their OneDrive instead of copying it to Moodle. The file remains in OneDrive and Moodle stores only a reference link.
64+
65+
**Important Access Control Considerations:**
66+
* The file\'s existing OneDrive permissions are NOT changed.
67+
* Users accessing the link must have appropriate permissions in OneDrive to view the file.
68+
* It is the responsibility of the user adding the link to ensure proper access permissions.
69+
* If OneDrive permissions are not set correctly, other users may be unable to access the file.
70+
71+
**When to use this option:**
72+
* Files are already shared with the intended audience in Microsoft 365.
73+
* You want to maintain a single source of truth in OneDrive.
74+
* Storage space in Moodle is a concern.
75+
* Edits to the OneDrive file should be reflected in Moodle.
76+
77+
Check this box to disable this option and require all files to be copied to Moodle.';
78+
$string['enabledirectlinkwarning'] = '<div class="alert alert-info"><strong>Note:</strong> When using the "Link to the file" option, no sharing setting changes are made to the OneDrive file. Users adding the file must ensure that file permissions in OneDrive are set correctly.</div>';
79+
3180
$string['erroraccessdenied'] = 'Access denied';
3281
$string['errorauthoidcnotconfig'] = 'Please configure the OpenID Connect authentication plugin before attempting to use the Microsoft 365 repository.';
3382
$string['errorbadclienttype'] = 'Invalid client type.';
3483
$string['errorbadpath'] = 'Bad Path';
3584
$string['errorcoursenotfound'] = 'Course not found';
3685
$string['erroro365required'] = 'This file is currently only available to Microsoft 365 users.';
3786
$string['errorwhiledownload'] = 'An error occurred while downloading the file';
87+
$string['errorwhilesharing'] = 'An error occurred while creating a sharable link';
3888

3989
$string['file'] = 'File';
90+
$string['filelinkingheader'] = 'File Linking Options';
4091
$string['groups'] = 'Groups (Courses)';
4192
$string['myfiles'] = 'My OneDrive';
4293
$string['notconfigured'] = '<p class="error">To use this plugin, you must first configure the <a href="{$a}/admin/settings.php?section=local_o365">Microsoft 365 plugins</a></p>';
4394
$string['office365:view'] = 'View Microsoft 365 repository';
4495
$string['onedrivegroup'] = 'Disable My OneDrive folder in file picker';
96+
$string['controlledsharelinkdesc'] = 'Shared copy (organization members only)';
97+
$string['copiedfile'] = 'Copy of file';
98+
$string['directlinkdesc'] = 'Direct link (existing permissions)';
4599
$string['pluginname'] = 'Microsoft 365';
46100
$string['pluginname_help'] = 'A Microsoft 365 Repository';
47101
$string['privacy:metadata'] = 'This plugin communicates with the Microsoft 365 OneDrive API as the current user. Any files uploaded will be sent to the remote server';

0 commit comments

Comments
 (0)