Skip to content

Commit ebdcb9a

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 c785ba4 commit ebdcb9a

File tree

5 files changed

+507
-27
lines changed

5 files changed

+507
-27
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 = 2022112850;
29+
$plugin->version = 2022112850.01;
3030
$plugin->requires = 2022112800;
3131
$plugin->release = '4.1.11';
3232
$plugin->component = 'local_o365';

repository/office365/lang/en/repository_office365.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,53 @@
2727
$string['configplugin'] = 'Configure Microsoft 365 Repository';
2828
$string['coursegroup'] = 'Disable Groups (Courses) folder in file picker';
2929
$string['defaultgroupsfolder'] = 'Course Files';
30+
$string['disableanonymousshare'] = 'Disable "{$a}" option';
31+
$string['disableanonymousshare_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.
32+
33+
**How It Works:**
34+
* Creates a copy of the selected file with a " - Shared" suffix.
35+
* The copy is saved to the user\'s OneDrive (not the original location).
36+
* An organization-scoped sharing link is created for the copy.
37+
* The original file remains unchanged with its existing permissions.
38+
39+
**Access Control:**
40+
* Only members of your Microsoft 365 organization can access the fil.e
41+
* Anyone in the organization with the link can VIEW the file.
42+
* External users and anonymous users CANNOT access the file.
43+
* The link does not expire automatically.
44+
* The file owner can manage or revoke sharing from their OneDrive.
45+
46+
**When to Use:**
47+
* You want to share a file with all organization members.
48+
* You want to protect the original file from accidental changes.
49+
* Storage space in Moodle is a concern.
50+
* The file content is appropriate for organization-wide access.
51+
52+
**Important Notes:**
53+
* The copy operation may take a few seconds for large files.
54+
* Users should ensure the file content is appropriate for organization-wide sharing.
55+
* The copied file will appear in the user\'s OneDrive with " - Shared" suffix.
56+
* Changes made to the original file will NOT be reflected in the shared copy, and vice versa.
57+
58+
Check this box to disable this option and prevent users from creating organization-shared copies.';
59+
$string['disableanonymoussharewarning'] = '<div class="alert alert-info"><strong>Note:</strong> When using the "{$a}" 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>';
60+
$string['disabledirectlink'] = 'Disable "{$a}" option';
61+
$string['disabledirectlink_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.
62+
63+
**Important Access Control Considerations:**
64+
* The file\'s existing OneDrive permissions are NOT changed.
65+
* Users accessing the link must have appropriate permissions in OneDrive to view the file.
66+
* It is the responsibility of the user adding the link to ensure proper access permissions.
67+
* If OneDrive permissions are not set correctly, other users may be unable to access the file.
68+
69+
**When to use this option:**
70+
* Files are already shared with the intended audience in Microsoft 365.
71+
* You want to maintain a single source of truth in OneDrive.
72+
* Storage space in Moodle is a concern.
73+
* Edits to the OneDrive file should be reflected in Moodle.
74+
75+
Check this box to disable this option and require all files to be copied to Moodle.';
76+
$string['disabledirectlinkwarning'] = '<div class="alert alert-info"><strong>Note:</strong> When using the "{$a}" 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>';
3077

3178
$string['erroraccessdenied'] = 'Access denied';
3279
$string['errorauthoidcnotconfig'] = 'Please configure the OpenID Connect authentication plugin before attempting to use the Microsoft 365 repository.';
@@ -35,13 +82,18 @@
3582
$string['errorcoursenotfound'] = 'Course not found';
3683
$string['erroro365required'] = 'This file is currently only available to Microsoft 365 users.';
3784
$string['errorwhiledownload'] = 'An error occurred while downloading the file';
85+
$string['errorwhilesharing'] = 'An error occurred while creating a sharable link';
3886

3987
$string['file'] = 'File';
88+
$string['filelinkingheader'] = 'File linking options';
4089
$string['groups'] = 'Groups (Courses)';
4190
$string['myfiles'] = 'My OneDrive';
4291
$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>';
4392
$string['office365:view'] = 'View Microsoft 365 repository';
4493
$string['onedrivegroup'] = 'Disable My OneDrive folder in file picker';
94+
$string['controlledsharelinkdesc'] = 'Shared copy (organization members only)';
95+
$string['copiedfile'] = 'Copy of file';
96+
$string['directlinkdesc'] = 'Direct link (existing permissions)';
4597
$string['pluginname'] = 'Microsoft 365';
4698
$string['pluginname_help'] = 'A Microsoft 365 Repository';
4799
$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)